diff --git a/DEPS b/DEPS
index 3314948..07407e7 100644
--- a/DEPS
+++ b/DEPS
@@ -123,10 +123,6 @@
   # By default, do not check out src-internal. This can be overridden e.g. with
   # custom_vars.
   'checkout_src_internal': False,
-  #
-  # For git submodule migration purpose only, details in
-  # TODO(crbug.com/1423494): Remove once the migration is completed.
-  'include_src_internal_deps': False,
 
   # For super-internal deps. Set by the official builders.
   'checkout_google_internal': False,
@@ -241,7 +237,7 @@
   #
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-amd64-generic-chrome-skylab
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-arm-generic-chrome-skylab
-  'lacros_sdk_version': '15435.0.0',
+  'lacros_sdk_version': '15456.0.0',
 
   # Generate location tag metadata to include in tests result data uploaded
   # to ResultDB. This isn't needed on some configs and the tool that generates
@@ -253,7 +249,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:55cc876158c057cd64677f1a1a2573c36c6c3025',
+  'luci_go': 'git_revision:c53344de69401cd5d608484b37cd6545f328406c',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -282,7 +278,7 @@
   # reclient CIPD package
   'reclient_package': 'infra/rbe/client/',
   # reclient CIPD package version
-  'reclient_version': 're_client_version:0.103.0.3dfc6d2-gomaip',
+  'reclient_version': 're_client_version:0.105.0.d6a0caf-gomaip',
 
   # Fetch siso CIPD package
   'checkout_siso': False,
@@ -304,15 +300,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'c207c1bdd5be723c1e18b17efcaa9d262e42b51d',
+  'skia_revision': '0b929ada9cc6b45044494f4b60de4e7c4c059719',
   # 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': 'd8a837cde2e8b7cfa56f1643719d8ad2d4a2f05e',
+  'v8_revision': '429a16fb62cdb83ae4b6a448f42acfccdbbcabc7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '39ac3fab8d1000a521da7d94644812435efba85a',
+  'angle_revision': '86e43cd7902c2c9bfb9db07421797bd28bc1ad6f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -320,7 +316,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': 'e37bff1a0f164d65b3cc646d65765ad733df0198',
+  'pdfium_revision': '4c16842f61a150b40c4dd104775dec19225ae7fe',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -331,7 +327,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:12.20230509.4.1',
+  'fuchsia_version': 'version:12.20230511.4.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -375,7 +371,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '70baabbf9304ff509407aba258e4b1e2f20e074c',
+  'catapult_revision': '829e9b448a2fd480fe2c2287b5c738ec5c56f7cb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -391,7 +387,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '51bff05f036b45846ec67bb3a152230c81ace347',
+  'devtools_frontend_revision': '246b734dba7bd48496df11ea80c400f96698fe88',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -431,7 +427,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': 'b8233deafeb931ead30ec407bf4c59b34c1981f8',
+  'dawn_revision': '6ac063d56817c449eed803268420e52cd83487d6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -451,7 +447,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libavif
   # and whatever else without interference from each other.
-  'libavif_revision': 'e81008f2ca09bcecfc37c26203da2dea1896350d',
+  'libavif_revision': 'd78c0db95b1afe85a66b41c066f8327165a8d567',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
@@ -467,7 +463,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'cros_components_revision': '11c179a6b1862edb6228bf474e16e2df45ae738b',
+  'cros_components_revision': '9cd2e615d994d9b32c294c72de6cb987316005cd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -796,12 +792,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'fa649039181e35c435de271469295daa55c10852',
+    'bfd76b01fee12e06fce2225eed4e884846b69ce9',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'e9fc223d065fb7a420aa49a9fa5935c78549c12b',
+    'url': Var('chromium_git') + '/website.git' + '@' + '07d8c86b470e10c6d4d53716ff24e34eebfa1c13',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -895,7 +891,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': '1HQv2efViD98yANoT-hHFtTP2WjbMM_C4kNA2ZGPL7kC',
+          'version': 'ajT7sS8M38Uueu0m3pXjsqEsM4NiGmIaUyk1fT5mqWcC',
         },
       ],
       'dep_type': 'cipd',
@@ -906,7 +902,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'MXgEsfu4itz-ZSmTvacF6xbE9XkkVh5BBpGRdX5VGKQC',
+          'version': 'CJY02ZgDk636OL7fQWshEzEv1psVIFryE6m3u59f0YMC',
         },
       ],
       'dep_type': 'cipd',
@@ -917,7 +913,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': 'TkqKOKFVbvnmyXX89FPmBkT1UpUgpXrE2OiiK9Akw6MC',
+          'version': 'pSaq7ewNWs_rdxtV1R78rfpPDLDAl7TvGllx_g7MXWYC',
         },
       ],
       'dep_type': 'cipd',
@@ -985,7 +981,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'cWeOwjRKma7lDUK3d9abHJdRCaMCkbmACOqfWXKqU5cC',
+          'version': 'pBUo7XJezpbmXFJqomJNLLtpHu5q3NrY77QhB5u0oQQC',
       },
     ],
     'condition': 'checkout_android',
@@ -1045,7 +1041,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/lint',
-               'version': 'uCylUrC5eRYlMPJJhaQVDtSIVVxyY78Rn1_VRuEl61gC',
+               'version': 'MTFSl9JaPMnHhDPun-Ry9w5lM4HeEVlSFebjgzoraCQC',
           },
       ],
       'condition': 'checkout_android',
@@ -1188,7 +1184,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' + '@' + 'c204c2c40869f5be9a2932efe8550a479cca1004',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '802eaf809650efa726068b2680ae3a19abc8c840',
       'condition': 'checkout_chromeos',
   },
 
@@ -1206,7 +1202,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '85f8020163b1bae794ab8ee38c943e17823f416e',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '86188ca58ad32e0e5b4af860c6399f160279b307',
       'condition': 'checkout_linux',
   },
 
@@ -1220,13 +1216,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'db74c58138a0c9b429c0eed0b316e6d1fabfba5a',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '24996afd00de79d33cea18204fc75ea6c0ad35c5',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '62d0046d44bde48dd88c4da97d5369e4036f3298',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '80b2b5372afacb821b723f6ffba6071a4d5be2c1',
     'condition': 'checkout_src_internal',
   },
 
@@ -1234,7 +1230,7 @@
     Var('chromium_git') + '/chromium/dom-distiller/dist.git' + '@' + '199de96b345ada7c6e7e6ba3d2fa7a6911b8767d',
 
   'src/third_party/eigen3/src':
-    Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '0b51f763cbbd0ed08168f88972724329f0375498',
+    Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '2709f4c8fbbe71a5383de1ba27e9833f218a642d',
 
   'src/third_party/emoji-metadata/src': {
     'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + '045f146fca682a836e01cd265171312bfb300e06',
@@ -1484,7 +1480,7 @@
     Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'e8712e415627f22d0b00ebee8db99547077f39bd',
 
   'src/third_party/libaom/source/libaom':
-    Var('aomedia_git') + '/aom.git' + '@' +  '6009df0c9db34ec4776e1b49288bbdf8fe0e3e5c',
+    Var('aomedia_git') + '/aom.git' + '@' +  'f15d8bacc72479979182f7484e0b80cf988ffd67',
 
   'src/third_party/libavif/src':
     Var('chromium_git') + '/external/github.com/AOMediaCodec/libavif.git' + '@' + Var('libavif_revision'),
@@ -1557,7 +1553,7 @@
     Var('chromium_git') + '/webm/libwebp.git' + '@' +  'fd7b5d48464475408d32d2611bdb6947d4246b97',
 
   'src/third_party/libyuv':
-    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '5c36ff76f1ce6550294c9c8b7777ffce15473b76',
+    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '6a68b18a9680ddb16b1397118675c146c6afbd65',
 
   'src/third_party/lighttpd': {
       'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'),
@@ -1693,7 +1689,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e04127739c5e542d7db6b93cb33d908cee9983ef',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f1b7df4d45aef9b9c96193319759a8c96c4533ca',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1733,7 +1729,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': '0gcMolQsxjUMbPA45S29WIXedlOUN8pu5W2H2cuJxwYC',
+              'version': 'abiZREVM1fg-Xrkh7hOo6XBXRWEkkaCsMd9Mk902_L0C',
           },
       ],
       'condition': 'checkout_android',
@@ -1771,7 +1767,7 @@
   },
 
   'src/third_party/ruy/src':
-    Var('chromium_git') + '/external/github.com/google/ruy.git' + '@' + '363f252289fb7a1fba1703d99196524698cb884d',
+    Var('chromium_git') + '/external/github.com/google/ruy.git' + '@' + 'c19139f55a94493086561288b243eaeec9d58353',
 
   'src/third_party/skia':
     Var('skia_git') + '/skia.git' + '@' +  Var('skia_revision'),
@@ -1825,7 +1821,7 @@
     Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + 'c036420683f672d685e27415de0a5f5e85bdc23f',
 
   'src/third_party/tflite/src':
-    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '9becb72d9b656f48de2bf00094910189434bc21e',
+    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + 'cdd9eec54ee64375548eafeb11623c71b2733d97',
 
   'src/third_party/turbine': {
       'packages': [
@@ -1838,7 +1834,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@c84ccdc63da162e4295ec552196501cd7351948e',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@729c65ec74f65931b4dfcd52b05bf9c0eef73430',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1875,7 +1871,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e8dbfc3f48b4605bd0eb5bfb7c361434480b55fd',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '4fd26fe4abb31b74e8b5cc9ea6ff0e36970eb7bf',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + 'a79bc6ee47446865a229e69d835ddcd0b9d39c8e',
@@ -1968,7 +1964,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': Var('chrome_git') + '/chrome/src-internal.git@0c704fa62198ab08c2280e3b0749fc598351c6aa',
+    'url': Var('chrome_git') + '/chrome/src-internal.git@202119aaf62a3778d3c3216e1c3ab92a700da289',
     'condition': 'checkout_src_internal',
   },
 
@@ -1998,7 +1994,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': '3MrPkELKn2N-dFJHXELe2WZLTgJ4G9CC-kW9z1cIRZ0C',
+        'version': 'MjTUvfGyFaUZkx9crzZR8t1MQK6oayf8iMIGn3KG_OYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2009,7 +2005,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'MIyyB91sSIAFT8SAu6XCJi0eajWmZwNrMkjdnWVkesMC',
+        'version': 'bPs5vp1jOH4akpgvlB8bb7dXkH6hr8V2_lfmxpKfI9QC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2031,7 +2027,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'I5bPsXwE_x8V6gBxZOrEf9pBjfuIWX6N9MhW-gADsYUC',
+        'version': 'CzB5aJshRuj3w-qwumS7kZVTZH3-0EbiB06Ib_1nEPAC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4101,7 +4097,7 @@
 
   'src/components/autofill/core/browser/form_parsing/internal_resources': {
       'url': Var('chrome_git') + '/chrome/components/autofill_regex_patterns.git' + '@' +
-        '7569017a0b3644101e38fc15d356899973f6ab7d',
+        '96efaea380a542c04a86c34a6b0d50bbb65e518a',
       'condition': 'checkout_src_internal',
   },
 
@@ -4167,7 +4163,7 @@
 
   'src/ios_internal':  {
       'url': '{chrome_git}/chrome/ios_internal.git' + '@' +
-        '528b5a97e8e055eb9cf61485e0efa38ee071cc8f',
+        '1ec92ade73656e4bc4561102182e470c211879f1',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/browser/cookie_manager.cc b/android_webview/browser/cookie_manager.cc
index f33080d..4581653a 100644
--- a/android_webview/browser/cookie_manager.cc
+++ b/android_webview/browser/cookie_manager.cc
@@ -539,14 +539,21 @@
                                              base::OnceClosure complete) {
   net::CookieOptions options = net::CookieOptions::MakeAllInclusive();
 
+  // TODO(crbug.com/1225444): Complete partitioned cookies implementation for
+  // WebView. The current implementation is a temporary fix for
+  // crbug.com/1442333 to let the app access its 1p partitioned cookie.
   if (GetMojoCookieManager()) {
     GetMojoCookieManager()->GetCookieList(
-        host, options, net::CookiePartitionKeyCollection::Todo(),
+        host, options,
+        net::CookiePartitionKeyCollection(
+            net::CookiePartitionKey::FromWire(net::SchemefulSite(host))),
         base::BindOnce(&CookieManager::GetCookieListCompleted,
                        base::Unretained(this), std::move(complete), result));
   } else {
     GetCookieStore()->GetCookieListWithOptionsAsync(
-        host, options, net::CookiePartitionKeyCollection::Todo(),
+        host, options,
+        net::CookiePartitionKeyCollection(
+            net::CookiePartitionKey::FromWire(net::SchemefulSite(host))),
         base::BindOnce(&CookieManager::GetCookieListCompleted,
                        base::Unretained(this), std::move(complete), result));
   }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
index 2e8bb2d..ed61b4c 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
@@ -259,6 +259,31 @@
         }
     }
 
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView"})
+    public void testEmbedderCanSeePartitionedCookies() throws Throwable {
+        TestWebServer webServer = TestWebServer.start();
+        try {
+            // Set a partitioned cookie and an unpartitioned cookie to ensure that they are all
+            // visible to CookieManager in the app.
+            String cookies[] = {"partitioned_cookie=foo; SameSite=None; Secure; Partitioned",
+                    "unpartitioned_cookie=bar; SameSite=None; Secure"};
+            List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>();
+            for (String cookie : cookies) {
+                responseHeaders.add(Pair.create("Set-Cookie", cookie));
+            }
+            String url = webServer.setResponse("/", "test", responseHeaders);
+            mActivityTestRule.loadUrlSync(
+                    mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+            waitForCookie(url);
+            assertHasCookies(url);
+            validateCookies(url, "partitioned_cookie", "unpartitioned_cookie");
+        } finally {
+            webServer.shutdown();
+        }
+    }
+
     private void setCookieWithDocumentCookieAPI(final String name, final String value)
             throws Throwable {
         JSUtils.executeJavaScriptAndWaitForResult(InstrumentationRegistry.getInstrumentation(),
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
index 4782b66..aa96d5c2 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
@@ -14,6 +14,7 @@
 import androidx.javascriptengine.EvaluationResultSizeLimitExceededException;
 import androidx.javascriptengine.IsolateStartupParameters;
 import androidx.javascriptengine.IsolateTerminatedException;
+import androidx.javascriptengine.JavaScriptConsoleCallback;
 import androidx.javascriptengine.JavaScriptIsolate;
 import androidx.javascriptengine.JavaScriptSandbox;
 import androidx.javascriptengine.MemoryLimitExceededException;
@@ -36,8 +37,10 @@
 
 import java.nio.charset.StandardCharsets;
 import java.util.Vector;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Instrumentation test for JavaScriptSandbox.
@@ -53,6 +56,28 @@
     private static final long REASONABLE_HEAP_SIZE = 100 * 1024 * 1024;
     private static final int LARGE_NAMED_DATA_SIZE = 2 * 1024 * 1024;
 
+    // ASCII, embedded null, Latin-1 supplement, code points above 0xff, and surrogate pairs.
+    private static final String UNICODE_TEST_STRING =
+            "Hello \u0000 Hell\u00f3 \u4f60\u597d \ud83d\udc4b";
+    private static final String JS_UNICODE_TEST_STRING =
+            "'Hello \u0000 Hell\u00f3 \u4f60\u597d \ud83d\udc4b'";
+    // Prefer this unless you are deliberately testing script input. Sending the script in pure
+    // ASCII reduces the probability that there may be both input and output bugs which cancel each
+    // other out.
+    private static final String ASCII_ESCAPED_JS_UNICODE_TEST_STRING =
+            "'Hello \\u0000 Hell\\u00f3 \\u4f60\\u597d \\ud83d\\udc4b'";
+
+    private static void assertStringEndsWithValidCodePoint(String string) {
+        Assert.assertNotNull(string);
+        if (string.length() == 0) {
+            return;
+        }
+        char lastChar = string.charAt(string.length() - 1);
+        Assert.assertFalse(Character.isHighSurrogate(lastChar));
+        // Reject replacement character
+        Assert.assertNotEquals(0xfffd, lastChar);
+    }
+
     @Test
     @MediumTest
     public void testSimpleJsEvaluation() throws Throwable {
@@ -1294,4 +1319,231 @@
             }
         }
     }
+
+    @Test
+    @MediumTest
+    public void testErrorSizeEnforced() throws Throwable {
+        final int maxSize = 100;
+        final Context context = ContextUtils.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT));
+            final IsolateStartupParameters settings = new IsolateStartupParameters();
+            settings.setMaxEvaluationReturnSizeBytes(maxSize);
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(settings)) {
+                // Errors which exceed the message threshold should preserve their error type but
+                // not their message.
+                //
+                // Don't test boundary cases as the exact error message is not necessarily
+                // well-defined.
+                final String largeError = "a".repeat(maxSize + 1);
+                final String largeErrorCode = "throw '" + largeError + "';";
+                final ListenableFuture<String> largeErrorResultFuture =
+                        jsIsolate.evaluateJavaScriptAsync(largeErrorCode);
+                try {
+                    largeErrorResultFuture.get(5, TimeUnit.SECONDS);
+                    Assert.fail("Should have thrown.");
+                } catch (ExecutionException e) {
+                    // Assert that the error type is preserved (and not replaced by a size error).
+                    Assert.assertTrue(
+                            e.getCause().getClass().equals(EvaluationFailedException.class));
+                    // Assert that some of the error message is preserved...
+                    Assert.assertTrue(e.getCause().getMessage().contains("aaaaaaaaaaaaaaaa"));
+                    // ... but not all of it.
+                    Assert.assertFalse(e.getCause().getMessage().contains(largeError));
+                    final int messageUtf8ByteLength =
+                            e.getCause().getMessage().getBytes(StandardCharsets.UTF_8).length;
+                    // Our truncation may chop off a complete UTF-8 code point (only 1 byte here).
+                    Assert.assertTrue(messageUtf8ByteLength >= maxSize - 1);
+                    Assert.assertTrue(messageUtf8ByteLength <= maxSize);
+                }
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnicodeResult() throws Throwable {
+        final Context context = ContextUtils.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
+                JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+            final ListenableFuture<String> resultFuture =
+                    jsIsolate.evaluateJavaScriptAsync(ASCII_ESCAPED_JS_UNICODE_TEST_STRING);
+            final String result = resultFuture.get(5, TimeUnit.SECONDS);
+
+            Assert.assertEquals(UNICODE_TEST_STRING, result);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnicodeError() throws Throwable {
+        final Context context = ContextUtils.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
+                JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+            final ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(
+                    "throw " + ASCII_ESCAPED_JS_UNICODE_TEST_STRING);
+            try {
+                resultFuture.get(5, TimeUnit.SECONDS);
+                Assert.fail("Should have thrown.");
+            } catch (ExecutionException e) {
+                Assert.assertTrue(e.getCause().getClass().equals(EvaluationFailedException.class));
+                Assert.assertTrue(e.getCause().getMessage().contains(UNICODE_TEST_STRING));
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnicodeScript() throws Throwable {
+        final Context context = ContextUtils.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
+                JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT));
+            // Test evaluation using String
+            {
+                final ListenableFuture<String> resultFuture =
+                        jsIsolate.evaluateJavaScriptAsync(JS_UNICODE_TEST_STRING);
+                final String result = resultFuture.get(5, TimeUnit.SECONDS);
+
+                Assert.assertEquals(UNICODE_TEST_STRING, result);
+            }
+
+            // Test evaluation using UTF-8 byte[]
+            {
+                final byte[] codeBytes = JS_UNICODE_TEST_STRING.getBytes(StandardCharsets.UTF_8);
+                final ListenableFuture<String> resultFuture =
+                        jsIsolate.evaluateJavaScriptAsync(codeBytes);
+                final String result = resultFuture.get(5, TimeUnit.SECONDS);
+
+                Assert.assertEquals(UNICODE_TEST_STRING, result);
+            }
+
+            // Assert that the byte[] API treats ISO_8859_1 (Latin-1) encoded Latin-1
+            // supplement characters as invalid UTF-8. (Replaced by U+FFFD replacement character.)
+            {
+                final byte[] codeBytes = "'Hell\u00f3'".getBytes(StandardCharsets.ISO_8859_1);
+                final ListenableFuture<String> resultFuture =
+                        jsIsolate.evaluateJavaScriptAsync(codeBytes);
+                final String result = resultFuture.get(5, TimeUnit.SECONDS);
+
+                Assert.assertEquals("Hell\ufffd", result);
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnicodeConsoleMessage() throws Throwable {
+        final Context context = ContextUtils.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
+                JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT));
+            // Test a small console message
+            {
+                final String code = "console.log(" + ASCII_ESCAPED_JS_UNICODE_TEST_STRING + ");";
+                final AtomicReference<String> messageBody = new AtomicReference<String>(null);
+                final CountDownLatch latch = new CountDownLatch(1);
+                jsIsolate.setConsoleCallback(new JavaScriptConsoleCallback() {
+                    @Override
+                    public void onConsoleMessage(JavaScriptConsoleCallback.ConsoleMessage message) {
+                        messageBody.set(message.getMessage());
+                        latch.countDown();
+                    }
+                });
+                jsIsolate.evaluateJavaScriptAsync(code).get(5, TimeUnit.SECONDS);
+
+                Assert.assertTrue(latch.await(2, TimeUnit.SECONDS));
+                Assert.assertEquals(UNICODE_TEST_STRING, messageBody.get());
+            }
+            // Test a large message.
+            // Test that truncation of Unicode doesn't result in a crash (but ignore exact result).
+            // The truncation length is not defined as part of the API (or Binder). Just try
+            // something significantly larger than the typical 1MB Binder memory limit.
+            // The truncationUpperBound is measured in bytes.
+            final int truncationUpperBound = 1024 * 1024;
+            for (int byteOffset = 0; byteOffset < 4; byteOffset++) {
+                // \ud83d\udc4b (waving hand sign) is 4 bytes in both UTF-8 and UTF-16.
+                final String longString = "a".repeat(byteOffset)
+                        + "\ud83d\udc4b".repeat(truncationUpperBound / 4 + 1)
+                        + "a".repeat(byteOffset);
+                final String code = "console.log('" + longString + "');";
+                final AtomicReference<String> messageBody = new AtomicReference<String>(null);
+                final CountDownLatch latch = new CountDownLatch(1);
+                jsIsolate.setConsoleCallback(new JavaScriptConsoleCallback() {
+                    @Override
+                    public void onConsoleMessage(JavaScriptConsoleCallback.ConsoleMessage message) {
+                        messageBody.set(message.getMessage());
+                        latch.countDown();
+                    }
+                });
+                jsIsolate.evaluateJavaScriptAsync(code).get(5, TimeUnit.SECONDS);
+
+                Assert.assertTrue(
+                        "Timeout with byteOffset " + byteOffset, latch.await(2, TimeUnit.SECONDS));
+                final int messageUtf8ByteLength =
+                        messageBody.get().getBytes(StandardCharsets.UTF_8).length;
+                Assert.assertTrue("messageUtf8ByteLength too large with byteOffset " + byteOffset,
+                        messageUtf8ByteLength <= truncationUpperBound);
+                assertStringEndsWithValidCodePoint(messageBody.get());
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnicodeErrorTruncation() throws Throwable {
+        // Test that truncation of Unicode doesn't result in a crash (but ignore exact result).
+        final int maxSize = 100;
+        final Context context = ContextUtils.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceForTestingAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT));
+            final IsolateStartupParameters settings = new IsolateStartupParameters();
+            settings.setMaxEvaluationReturnSizeBytes(maxSize);
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(settings)) {
+                for (int byteOffset = 0; byteOffset < 4; byteOffset++) {
+                    final String longString = "a".repeat(byteOffset)
+                            + "\ud83d\udc4b".repeat(maxSize) + "a".repeat(byteOffset);
+                    final String code = "throw '" + longString + "';";
+                    final ListenableFuture<String> resultFuture =
+                            jsIsolate.evaluateJavaScriptAsync(code);
+                    try {
+                        resultFuture.get(5, TimeUnit.SECONDS);
+                        Assert.fail("Should have thrown with byteOffset " + byteOffset);
+                    } catch (ExecutionException e) {
+                        Assert.assertTrue("Bad exception with byteOffset " + byteOffset,
+                                e.getCause().getClass().equals(EvaluationFailedException.class));
+                        final int messageUtf8ByteLength =
+                                e.getCause().getMessage().getBytes(StandardCharsets.UTF_8).length;
+                        // Our truncation may chop off a complete or incomplete multi-byte code
+                        // point.
+                        Assert.assertTrue(
+                                "messageUtf8ByteLength too small with byteOffset " + byteOffset,
+                                messageUtf8ByteLength >= maxSize - 4);
+                        Assert.assertTrue(
+                                "messageUtf8ByteLength too large with byteOffset " + byteOffset,
+                                messageUtf8ByteLength <= maxSize);
+                        assertStringEndsWithValidCodePoint(e.getCause().getMessage());
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java
index be838b69..a280733 100644
--- a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java
+++ b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java
@@ -99,6 +99,21 @@
         }
     }
 
+    // Roughly truncate a (Unicode) Java string, avoiding truncation in the middle of a surrogate
+    // pair. Note that this is fairly naive and doesn't deal with any additional complexities of
+    // Unicode, such as characters composed of multiple code points, modifiers, ...
+    //
+    // maxCodePoints must be > 0.
+    private static String truncateUnicodeString(String original, int maxLength) {
+        if (original == null || original.length() <= maxLength) {
+            return original;
+        }
+        if (Character.isHighSurrogate(original.charAt(maxLength - 1))) {
+            maxLength--;
+        }
+        return original.substring(0, maxLength);
+    }
+
     // Called by isolate thread
     @CalledByNative
     public void consoleMessage(int contextGroupId, int level, String message, String source,
@@ -107,18 +122,14 @@
         if (callback == null) {
             return;
         }
+        // Note these are measured in chars (not bytes), so in the worst case the Binder parcel size
+        // may be a little larger than 2 * (32768 + 4069 + 16348) = 106496.
         final int messageLimit = 32768;
         final int sourceLimit = 4096;
         final int traceLimit = 16384;
-        if (message != null && message.length() > messageLimit) {
-            message = message.substring(0, messageLimit);
-        }
-        if (source != null && source.length() > sourceLimit) {
-            source = source.substring(0, sourceLimit);
-        }
-        if (trace != null && trace.length() > traceLimit) {
-            trace = trace.substring(0, traceLimit);
-        }
+        message = truncateUnicodeString(message, messageLimit);
+        source = truncateUnicodeString(source, sourceLimit);
+        trace = truncateUnicodeString(trace, traceLimit);
         try {
             callback.consoleMessage(contextGroupId, level, message, source, line, column, trace);
         } catch (RemoteException e) {
diff --git a/android_webview/tools/system_webview_shell/apk/res/menu/main_menu.xml b/android_webview/tools/system_webview_shell/apk/res/menu/main_menu.xml
index fb868c0..ca7d797 100644
--- a/android_webview/tools/system_webview_shell/apk/res/menu/main_menu.xml
+++ b/android_webview/tools/system_webview_shell/apk/res/menu/main_menu.xml
@@ -10,6 +10,8 @@
           android:title="@string/menu_reset_webview"/>
     <item android:id="@+id/menu_clear_cache"
           android:title="@string/menu_clear_cache"/>
+    <item android:id="@+id/menu_get_cookie"
+          android:title="@string/menu_get_cookie"/>
     <item android:id="@+id/menu_enable_tracing"
           android:checkable="true"
           android:title="@string/menu_enable_tracing"/>
diff --git a/android_webview/tools/system_webview_shell/apk/res/values/strings.xml b/android_webview/tools/system_webview_shell/apk/res/values/strings.xml
index 83dd6264..e019c69a 100644
--- a/android_webview/tools/system_webview_shell/apk/res/values/strings.xml
+++ b/android_webview/tools/system_webview_shell/apk/res/values/strings.xml
@@ -18,6 +18,7 @@
     <string name="menu_reload_webview">Reload</string>
     <string name="menu_reset_webview">Destroy and create new WebView</string>
     <string name="menu_clear_cache">Clear cache</string>
+    <string name="menu_get_cookie">Get Cookie</string>
     <string name="menu_enable_tracing">Enable tracing</string>
     <string name="menu_force_dark_off">Force Dark Off</string>
     <string name="menu_force_dark_auto">Force Dark Auto</string>
diff --git a/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewBrowserActivity.java b/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewBrowserActivity.java
index dd15bf76..acc6959 100644
--- a/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewBrowserActivity.java
+++ b/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewBrowserActivity.java
@@ -712,6 +712,10 @@
                 mWebView.clearCache(true);
             }
             return true;
+        } else if (itemId == R.id.menu_get_cookie) {
+            String cookie = CookieManager.getInstance().getCookie(mWebView.getUrl());
+            Log.w(TAG, "GetCookie: " + cookie);
+            return true;
         } else if (itemId == R.id.menu_enable_tracing) {
             mEnableTracing = !mEnableTracing;
             item.setChecked(mEnableTracing);
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 0ba9fa7..70d1241 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -607,6 +607,7 @@
     "game_dashboard/game_dashboard_controller.cc",
     "game_dashboard/game_dashboard_controller.h",
     "game_dashboard/game_dashboard_delegate.h",
+    "glanceables/classroom/glanceables_classroom_client.h",
     "glanceables/glanceables_controller.cc",
     "glanceables/glanceables_controller.h",
     "glanceables/glanceables_delegate.h",
@@ -2713,7 +2714,7 @@
     "//chromeos/ui/clipboard_history",
     "//chromeos/ui/frame",
     "//chromeos/ui/wm",
-    "//components/access_code_cast/common",
+    "//components/access_code_cast/common:metrics",
     "//components/account_id",
     "//components/app_constants",
     "//components/app_restore",
@@ -3795,6 +3796,7 @@
     "shelf/shelf_layout_manager_pixeltest.cc",
     "system/accessibility/accessibility_detailed_view_pixeltest.cc",
     "system/audio/audio_detailed_view_pixeltest.cc",
+    "system/bluetooth/bluetooth_detailed_view_impl_pixeltest.cc",
     "system/bluetooth/bluetooth_detailed_view_legacy_pixeltest.cc",
     "system/brightness/display_detailed_view_pixeltest.cc",
     "system/cast/cast_detailed_view_pixeltest.cc",
@@ -3808,6 +3810,7 @@
     "system/time/calendar_unittest_utils.cc",
     "system/time/calendar_unittest_utils.h",
     "system/time/calendar_up_next_pixeltest.cc",
+    "system/unified/feature_tile_pixeltest.cc",
     "system/unified/power_button_pixeltest.cc",
     "test/pixel/demo_ash_pixel_diff_test.cc",
     "user_education/views/help_bubble_view_ash_pixeltest.cc",
diff --git a/ash/ambient/ambient_controller_unittest.cc b/ash/ambient/ambient_controller_unittest.cc
index 4526367..4dc10a2 100644
--- a/ash/ambient/ambient_controller_unittest.cc
+++ b/ash/ambient/ambient_controller_unittest.cc
@@ -23,6 +23,7 @@
 #include "ash/constants/ambient_video.h"
 #include "ash/constants/ash_features.h"
 #include "ash/login/login_screen_controller.h"
+#include "ash/login/ui/lock_screen.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
 #include "ash/public/cpp/ambient/ambient_ui_model.h"
 #include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h"
@@ -35,9 +36,10 @@
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
 #include "base/location.h"
-#include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
 #include "base/task/sequenced_task_runner.h"
@@ -50,13 +52,14 @@
 #include "build/buildflag.h"
 #include "chromeos/ash/components/assistant/buildflags.h"
 #include "chromeos/ash/services/libassistant/public/cpp/assistant_interaction_metadata.h"
-#include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/dbus/power_manager/suspend.pb.h"
 #include "net/base/url_util.h"
+#include "ui/base/user_activity/user_activity_detector.h"
 #include "ui/events/event.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/events/platform/platform_event_source.h"
 #include "ui/events/pointer_details.h"
+#include "ui/events/test/event_generator.h"
 #include "ui/events/types/event_type.h"
 
 namespace ash {
@@ -67,6 +70,38 @@
 constexpr char kUser1[] = "user1@gmail.com";
 constexpr char kUser2[] = "user2@gmail.com";
 
+std::vector<base::OnceClosure> GetEventGeneratorCallbacks(
+    ui::test::EventGenerator* event_generator) {
+  std::vector<base::OnceClosure> event_callbacks;
+
+  event_callbacks.push_back(
+      base::BindOnce(&ui::test::EventGenerator::ClickLeftButton,
+                     base::Unretained(event_generator)));
+
+  event_callbacks.push_back(
+      base::BindOnce(&ui::test::EventGenerator::ClickRightButton,
+                     base::Unretained(event_generator)));
+
+  event_callbacks.push_back(
+      base::BindOnce(&ui::test::EventGenerator::DragMouseBy,
+                     base::Unretained(event_generator), /*dx=*/10,
+                     /*dy=*/10));
+
+  event_callbacks.push_back(
+      base::BindOnce(&ui::test::EventGenerator::GestureScrollSequence,
+                     base::Unretained(event_generator),
+                     /*start=*/gfx::Point(10, 10),
+                     /*end=*/gfx::Point(20, 10),
+                     /*step_delay=*/base::Milliseconds(10),
+                     /*steps=*/1));
+
+  event_callbacks.push_back(
+      base::BindOnce(&ui::test::EventGenerator::PressTouch,
+                     base::Unretained(event_generator), absl::nullopt));
+
+  return event_callbacks;
+}
+
 class AmbientUiVisibilityBarrier : public AmbientUiModelObserver {
  public:
   explicit AmbientUiVisibilityBarrier(AmbientUiVisibility target_visibility)
@@ -143,10 +178,6 @@
     return ui_model_bound;
   }
 
-  bool IsInactivityTimerRunning() {
-    return ambient_controller()->inactivity_timer_.IsRunning();
-  }
-
   base::test::ScopedFeatureList feature_list_;
 
  protected:
@@ -685,70 +716,115 @@
                    device::mojom::WakeLockType::kPreventDisplaySleep));
 }
 
-// TODO(cowmoo): find a way to simulate events to trigger |UserActivityDetector|
-TEST_P(AmbientControllerTestForAnyUiSettings,
-       ShouldDismissContainerViewOnEvents) {
-  std::vector<std::unique_ptr<ui::Event>> events;
+TEST_P(AmbientControllerTestForAnyUiSettings, ShouldCloseOnEvent) {
+  auto* ambient_ui_model = AmbientUiModel::Get();
 
-  for (auto mouse_event_type : {ui::ET_MOUSE_PRESSED, ui::ET_MOUSE_MOVED}) {
-    events.emplace_back(std::make_unique<ui::MouseEvent>(
-        mouse_event_type, gfx::Point(), gfx::Point(), base::TimeTicks(),
-        ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE));
-  }
+  std::vector<base::OnceClosure> event_callbacks =
+      GetEventGeneratorCallbacks(GetEventGenerator());
 
-  events.emplace_back(std::make_unique<ui::MouseWheelEvent>(
-      gfx::Vector2d(), gfx::PointF(), gfx::PointF(), base::TimeTicks(),
-      ui::EF_MIDDLE_MOUSE_BUTTON, ui::EF_NONE));
-
-  events.emplace_back(std::make_unique<ui::ScrollEvent>(
-      ui::ET_SCROLL, gfx::PointF(), gfx::PointF(), base::TimeTicks(),
-      ui::EF_NONE, /*x_offset=*/0.0f,
-      /*y_offset=*/0.0f,
-      /*x_offset_ordinal=*/0.0f,
-      /*x_offset_ordinal=*/0.0f, /*finger_count=*/2));
-
-  events.emplace_back(std::make_unique<ui::TouchEvent>(
-      ui::ET_TOUCH_PRESSED, gfx::PointF(), gfx::PointF(), base::TimeTicks(),
-      ui::PointerDetails()));
-
-  // External user activity.
-  events.emplace_back(nullptr);
-
-  for (const auto& event : events) {
+  for (auto& event_callback : event_callbacks) {
     SetAmbientShownAndWaitForWidgets();
     FastForwardTiny();
     EXPECT_TRUE(ambient_controller()->IsShowing());
 
-    if (!event) {
-      ambient_controller()->OnUserActivity(nullptr);
-    } else if (event.get()->IsMouseEvent()) {
-      ambient_controller()->OnMouseEvent(event.get()->AsMouseEvent());
-    } else if (event.get()->IsTouchEvent()) {
-      ambient_controller()->OnTouchEvent(event.get()->AsTouchEvent());
-    } else {
-      ambient_controller()->OnUserActivity(event.get());
-    }
+    std::move(event_callback).Run();
 
     FastForwardTiny();
+    EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
+    EXPECT_EQ(AmbientUiVisibility::kClosed, ambient_ui_model->ui_visibility());
     EXPECT_TRUE(GetContainerViews().empty());
-
-    // Clean up.
-    CloseAmbientScreen();
   }
 }
 
-TEST_P(AmbientControllerTestForAnyUiSettings, ShouldDismissAndThenComesBack) {
+TEST_P(AmbientControllerTestForAnyUiSettings,
+       ShouldDismissToLockScreenOnEvent) {
+  auto* ambient_ui_model = AmbientUiModel::Get();
+
+  std::vector<base::OnceClosure> event_callbacks =
+      GetEventGeneratorCallbacks(GetEventGenerator());
+
+  for (auto& event_callback : event_callbacks) {
+    // Lock screen and fast forward a bit to verify entered hidden state.
+    LockScreen();
+    FastForwardTiny();
+    EXPECT_EQ(AmbientUiVisibility::kHidden, ambient_ui_model->ui_visibility());
+
+    // Wait for timeout to elapse so ambient is shown.
+    FastForwardByLockScreenInactivityTimeout();
+    EXPECT_EQ(AmbientUiVisibility::kShouldShow,
+              ambient_ui_model->ui_visibility());
+    EXPECT_TRUE(ambient_controller()->IsShowing());
+
+    // Send an event.
+    std::move(event_callback).Run();
+    EXPECT_TRUE(GetContainerViews().empty());
+    EXPECT_EQ(AmbientUiVisibility::kHidden, ambient_ui_model->ui_visibility());
+    // The lock screen timer should have just restarted, so greater than 99% of
+    // time remaining on the timer until ambient restarts.
+    EXPECT_GT(GetRemainingLockScreenTimeoutFraction().value(), 0.99f);
+
+    // Wait the timeout duration again.
+    FastForwardByLockScreenInactivityTimeout();
+    FastForwardTiny();
+    // Ambient has started again due to elapsed timeout.
+    EXPECT_EQ(AmbientUiVisibility::kShouldShow,
+              ambient_ui_model->ui_visibility());
+    EXPECT_TRUE(ambient_controller()->IsShowing());
+
+    // Reset for next iteration.
+    UnlockScreen();
+  }
+}
+
+// Currently only runs for non-video screen saver settings due to needing to set
+// photo download delay.
+TEST_F(AmbientControllerTest, ShouldResetLockScreenInactivityTimerOnEvent) {
+  auto* ambient_ui_model = AmbientUiModel::Get();
+  // Set a long photo download delay so that state is
+  // `AmbientUiVisibility::kShown` but widget does not exist to receive events
+  // yet.
+  SetPhotoDownloadDelay(base::Seconds(1));
   LockScreen();
   FastForwardByLockScreenInactivityTimeout();
   FastForwardTiny();
-  EXPECT_TRUE(ambient_controller()->IsShowing());
+  // Ambient controller is shown but photos have not yet downloaded, so ambient
+  // widget and container views do not exist.
+  EXPECT_EQ(AmbientUiVisibility::kShouldShow,
+            ambient_ui_model->ui_visibility());
+  EXPECT_FALSE(ambient_controller()->IsShowing())
+      << "Ambient container views should not exist because photos not yet "
+         "downloaded";
+  // Inactivity timer has elapsed so nullopt.
+  EXPECT_FALSE(GetRemainingLockScreenTimeoutFraction().has_value());
 
-  GetEventGenerator()->PressLeftButton();
-  FastForwardTiny();
-  EXPECT_TRUE(GetContainerViews().empty());
+  // Send a user activity through `UserActivityDetector`. `EventGenerator` is
+  // not hooked up to `UserActivityDetector` in this test environment, so
+  // manually trigger `UserActivityDetector` ourselves.
+  auto* user_activity_detector = ui::UserActivityDetector::Get();
+  ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE);
+  user_activity_detector->DidProcessEvent(&event);
+  EXPECT_EQ(AmbientUiVisibility::kShouldShow, ambient_ui_model->ui_visibility())
+      << "Still shown because waiting for `OnKeyEvent` to be called";
 
-  FastForwardByLockScreenInactivityTimeout();
-  FastForwardTiny();
+  // Call `OnKeyEvent` via `EventGenerator`.
+  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_A);
+  EXPECT_EQ(AmbientUiVisibility::kHidden, ambient_ui_model->ui_visibility())
+      << "Should be kHidden because of recent OnKeyEvent call";
+  EXPECT_GT(GetRemainingLockScreenTimeoutFraction().value(), 0.99)
+      << "Lock screen inactivity timer should have restarted";
+
+  FastForwardByLockScreenInactivityTimeout(0.5);
+  EXPECT_GT(GetRemainingLockScreenTimeoutFraction().value(), 0.4);
+
+  FastForwardByLockScreenInactivityTimeout(0.51);
+  EXPECT_FALSE(GetRemainingLockScreenTimeoutFraction().has_value())
+      << "Inactivity timer has stopped";
+  EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
+  EXPECT_FALSE(ambient_controller()->IsShowing())
+      << "Photos still have not yet downloaded";
+
+  task_environment()->FastForwardBy(base::Seconds(2));
+  // Finally visible and running now that images downloaded.
   EXPECT_TRUE(ambient_controller()->IsShowing());
 }
 
@@ -1429,7 +1505,7 @@
 
   ui::MouseEvent mouse_event(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
                              base::TimeTicks(), ui::EF_NONE, ui::EF_NONE);
-  ambient_controller()->OnUserActivity(&mouse_event);
+  ui::UserActivityDetector::Get()->DidProcessEvent(&mouse_event);
   FastForwardTiny();
 
   EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
@@ -1502,38 +1578,6 @@
   EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
 }
 
-TEST_F(AmbientControllerTest,
-       ShouldResetInactivityTimerOnUserActivityWhileUiIsHidden) {
-  LockScreen();
-  FastForwardByLockScreenInactivityTimeout();
-  FastForwardTiny();
-  EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
-
-  HideAmbientScreen();
-  FastForwardTiny();
-  EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
-            AmbientUiVisibility::kHidden);
-
-  const base::TimeDelta inactivity_timeout =
-      ambient_controller()
-          ->ambient_ui_model()
-          ->lock_screen_inactivity_timeout();
-  task_environment()->FastForwardBy(inactivity_timeout * 0.5);
-  ambient_controller()->OnUserActivity(
-      std::make_unique<ui::KeyEvent>(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                     ui::EF_NONE)
-          .get());
-  EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
-
-  task_environment()->FastForwardBy(inactivity_timeout * 0.8);
-  EXPECT_TRUE(IsInactivityTimerRunning());
-  EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
-
-  task_environment()->FastForwardBy(inactivity_timeout * 0.3);
-  EXPECT_FALSE(IsInactivityTimerRunning());
-  EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
-}
-
 class AmbientControllerForManagedScreensaverTest : public AmbientAshTestBase {
  public:
   void SetUp() override {
@@ -1732,7 +1776,9 @@
       GetContainerView()->GetViewByID(AmbientViewID::kAmbientPhotoView));
 }
 
-class AmbientControllerForManagedScreensaverLoginScreenTest
+// TODO(b/271093537): Enable this tests once `ScreensaverImagesPolicyHandler`
+// supports the sign-in screen
+class DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest
     : public AmbientControllerForManagedScreensaverTest {
  public:
   void SetUp() override {
@@ -1754,7 +1800,7 @@
   }
 };
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        ShownOnLoginScreen) {
   TriggerLoginScreen();
 
@@ -1768,7 +1814,7 @@
   EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
 }
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        ShownOnLoginWhenPrefUpdatedLater) {
   SetAmbientModeManagedScreensaverEnabled(/*enabled=*/false);
   EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
@@ -1781,14 +1827,14 @@
   ASSERT_TRUE(GetContainerView());
 }
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        NotShownOnLoginScreenWhenDisabled) {
   SetAmbientModeManagedScreensaverEnabled(/*enabled=*/false);
   FastForwardByLockScreenInactivityTimeout();
   EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
 }
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        UserLogsInAmbientModeDisabledAndManagedAmbientModeEnabled) {
   TriggerLoginScreen();
   EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
@@ -1814,7 +1860,7 @@
   ASSERT_TRUE(GetContainerView());
 }
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        UserLogsInAmbientModeEnabled) {
   TriggerLoginScreen();
   EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
@@ -1833,7 +1879,7 @@
   ASSERT_TRUE(GetContainerView());
 }
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        ManagedScreensaverClosedWhenImagesCleared) {
   TriggerLoginScreen();
   EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
@@ -1862,7 +1908,7 @@
   EXPECT_FALSE(ambient_controller()->ShouldShowAmbientUi());
 }
 
-TEST_F(AmbientControllerForManagedScreensaverLoginScreenTest,
+TEST_F(DISABLED_AmbientControllerForManagedScreensaverLoginScreenTest,
        ManagedScreensaverClosedWhenImageLoadingFails) {
   TriggerLoginScreen();
   EXPECT_TRUE(ambient_controller()->ShouldShowAmbientUi());
diff --git a/ash/ambient/managed/screensaver_image_downloader.cc b/ash/ambient/managed/screensaver_image_downloader.cc
index fa7c1f4..206ca4f 100644
--- a/ash/ambient/managed/screensaver_image_downloader.cc
+++ b/ash/ambient/managed/screensaver_image_downloader.cc
@@ -106,9 +106,8 @@
 
 }  // namespace
 
-ScreensaverImageDownloader::Job::Job(const std::string& image_url,
-                                     ResultCallback result_callback)
-    : image_url(image_url), result_callback(std::move(result_callback)) {}
+ScreensaverImageDownloader::Job::Job(const std::string& image_url)
+    : image_url(image_url) {}
 
 ScreensaverImageDownloader::Job::~Job() = default;
 
@@ -120,12 +119,14 @@
 
 ScreensaverImageDownloader::ScreensaverImageDownloader(
     scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
-    const base::FilePath& download_directory)
+    const base::FilePath& download_directory,
+    ImageListUpdatedCallback image_list_updated_callback)
     : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
            base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
       shared_url_loader_factory_(shared_url_loader_factory),
-      download_directory_(download_directory) {}
+      download_directory_(download_directory),
+      image_list_updated_callback_(image_list_updated_callback) {}
 
 ScreensaverImageDownloader::~ScreensaverImageDownloader() = default;
 
@@ -163,6 +164,16 @@
                      download_directory_));
 }
 
+std::vector<base::FilePath> ScreensaverImageDownloader::GetScreensaverImages() {
+  return std::vector<base::FilePath>(downloaded_images_.begin(),
+                                     downloaded_images_.end());
+}
+
+void ScreensaverImageDownloader::SetImagesForTesting(
+    const std::vector<base::FilePath>& images_file_paths) {
+  downloaded_images_ = base::flat_set<base::FilePath>(images_file_paths);
+}
+
 base::FilePath ScreensaverImageDownloader::GetDowloadDirForTesting() {
   return download_directory_;
 }
@@ -276,8 +287,11 @@
     ScreensaverImageDownloadResult result,
     absl::optional<base::FilePath> path) {
   // TODO(b/276208772): Track result with metrics
-  CHECK(!download_job->result_callback.is_null());
-  std::move(download_job->result_callback).Run(result, path);
+  if (result == ScreensaverImageDownloadResult::kSuccess) {
+    downloaded_images_.insert(*path);
+    image_list_updated_callback_.Run(std::vector<base::FilePath>(
+        downloaded_images_.begin(), downloaded_images_.end()));
+  }
 
   if (downloading_queue_.empty()) {
     queue_state_ = QueueState::kWaiting;
diff --git a/ash/ambient/managed/screensaver_image_downloader.h b/ash/ambient/managed/screensaver_image_downloader.h
index a8f7258e..6926240 100644
--- a/ash/ambient/managed/screensaver_image_downloader.h
+++ b/ash/ambient/managed/screensaver_image_downloader.h
@@ -7,8 +7,10 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "ash/ash_export.h"
+#include "base/containers/flat_set.h"
 #include "base/containers/queue.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
@@ -50,19 +52,17 @@
   };
 
  public:
-  // Convenience definition for the callback provided by clients wanting to
-  // download images.
-  using ResultCallback =
-      base::OnceCallback<void(ScreensaverImageDownloadResult result,
-                              absl::optional<base::FilePath> path)>;
+  using ImageListUpdatedCallback =
+      base::RepeatingCallback<void(const std::vector<base::FilePath>& images)>;
 
   // Represents a single image download request from `image_url` to
   // `download_directory_`. Once this job has been completed, `result_callback`
   // will be invoked with the actual result, and the path to the downloaded file
   // if the operation succeeded.
+  // TODO(b/280810255): Delete this class, and use a plain std::string.
   struct Job {
     Job() = delete;
-    Job(const std::string& image_url, ResultCallback result_callback);
+    explicit Job(const std::string& image_url);
     ~Job();
 
     // Creates a unique name based on a hash operation on the image URL to
@@ -70,14 +70,14 @@
     std::string file_name() const;
 
     const std::string image_url;
-    ResultCallback result_callback;
   };
 
   ScreensaverImageDownloader() = delete;
 
   ScreensaverImageDownloader(
       scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
-      const base::FilePath& download_directory);
+      const base::FilePath& download_directory,
+      ImageListUpdatedCallback image_list_updated_callback);
 
   ~ScreensaverImageDownloader();
 
@@ -97,6 +97,11 @@
   // Clears out the download folder.
   void DeleteDownloadedImages();
 
+  std::vector<base::FilePath> GetScreensaverImages();
+
+  // Used for setting images in tests.
+  void SetImagesForTesting(const std::vector<base::FilePath>& images);
+
   base::FilePath GetDowloadDirForTesting();
 
  private:
@@ -144,10 +149,15 @@
   // jobs will be queued, and executed sequentially.
   base::queue<std::unique_ptr<Job>> downloading_queue_;
 
+  base::flat_set<base::FilePath> downloaded_images_;
+
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
   base::FilePath download_directory_;
 
+  // Used to notify changes in the list of downloaded images.
+  ImageListUpdatedCallback image_list_updated_callback_;
+
   base::WeakPtrFactory<ScreensaverImageDownloader> weak_ptr_factory_{this};
 };
 
diff --git a/ash/ambient/managed/screensaver_image_downloader_unittest.cc b/ash/ambient/managed/screensaver_image_downloader_unittest.cc
index 0dd19f63..94b6c95 100644
--- a/ash/ambient/managed/screensaver_image_downloader_unittest.cc
+++ b/ash/ambient/managed/screensaver_image_downloader_unittest.cc
@@ -15,8 +15,8 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/repeating_test_future.h"
 #include "base/test/task_environment.h"
-#include "base/test/test_future.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -35,12 +35,11 @@
 
 }  // namespace
 
-using DownloadResultFuture =
-    base::test::TestFuture<ScreensaverImageDownloadResult,
-                           absl::optional<base::FilePath>>;
-
 class ScreensaverImageDownloaderTest : public testing::Test {
  public:
+  using ImageListUpdatedFuture =
+      base::test::RepeatingTestFuture<const std::vector<base::FilePath>&>;
+
   ScreensaverImageDownloaderTest() = default;
 
   ScreensaverImageDownloaderTest(const ScreensaverImageDownloaderTest&) =
@@ -59,7 +58,7 @@
         std::make_unique<ScreensaverImageDownloader>(
             base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                 &url_loader_factory_),
-            test_download_folder_);
+            test_download_folder_, image_list_updated_future_.GetCallback());
   }
 
   ScreensaverImageDownloader* screensaver_image_downloader() {
@@ -83,15 +82,9 @@
               screensaver_image_downloader_->downloading_queue_.size());
   }
 
-  std::unique_ptr<DownloadResultFuture> QueueNewJobWithFuture(
-      const std::string& url) {
-    std::unique_ptr<DownloadResultFuture> future_callback =
-        std::make_unique<DownloadResultFuture>();
-    auto job = std::make_unique<ScreensaverImageDownloader::Job>(
-        url, future_callback->GetCallback());
+  void QueueNewImageDownload(const std::string& url) {
+    auto job = std::make_unique<ScreensaverImageDownloader::Job>(url);
     screensaver_image_downloader_->QueueDownloadJob(std::move(job));
-
-    return future_callback;
   }
 
   base::FilePath GetExpectedFilePath(const std::string url) {
@@ -101,21 +94,25 @@
   }
 
   void VerifySucessfulImageRequest(
-      std::unique_ptr<DownloadResultFuture> result_future,
-      const std::string& url,
-      const std::string& file_contents) {
-    ASSERT_TRUE(result_future.get());
-    ASSERT_TRUE(result_future->Wait()) << "Callback expected to be called.";
+      const std::vector<std::pair<base::FilePath, std::string>>&
+          expected_images) {
+    ASSERT_TRUE(image_list_updated_future_.Wait())
+        << "Callback expected to be called.";
 
-    auto [result, optional_path] = result_future->Take();
-    EXPECT_EQ(ScreensaverImageDownloadResult::kSuccess, result);
-    ASSERT_TRUE(optional_path.has_value());
-    EXPECT_EQ(GetExpectedFilePath(url), *optional_path);
+    const std::vector<base::FilePath> image_list =
+        image_list_updated_future_.Take();
+    ASSERT_EQ(expected_images.size(), image_list.size());
 
-    ASSERT_TRUE(base::PathExists(*optional_path));
-    std::string actual_file_contents;
-    EXPECT_TRUE(base::ReadFileToString(*optional_path, &actual_file_contents));
-    EXPECT_EQ(file_contents, actual_file_contents);
+    for (const auto& [path, file_content] : expected_images) {
+      bool found = std::find(image_list.begin(), image_list.end(), path) !=
+                   image_list.end();
+      ASSERT_TRUE(found);
+      ASSERT_TRUE(base::PathExists(path));
+
+      std::string actual_file_contents;
+      EXPECT_TRUE(base::ReadFileToString(path, &actual_file_contents));
+      EXPECT_EQ(file_content, actual_file_contents);
+    }
   }
 
  private:
@@ -124,53 +121,56 @@
   base::ScopedTempDir tmp_dir_;
   base::FilePath test_download_folder_;
   network::TestURLLoaderFactory url_loader_factory_;
+  ImageListUpdatedFuture image_list_updated_future_;
 
   // Class under test
   std::unique_ptr<ScreensaverImageDownloader> screensaver_image_downloader_;
 };
 
 TEST_F(ScreensaverImageDownloaderTest, DownloadImagesTest) {
-  // Test successful download.
-  url_loader_factory()->AddResponse(kImageUrl1, kFileContents);
-  VerifySucessfulImageRequest(QueueNewJobWithFuture(kImageUrl1), kImageUrl1,
-                              kFileContents);
-
-  // Test download with a fake network error.
-  {
-    auto response_head = network::mojom::URLResponseHead::New();
-    response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
-    response_head->headers->SetHeader("Content-Type", "image/jpg");
-    response_head->headers->ReplaceStatusLine("HTTP/1.1 404 Not found");
-    url_loader_factory()->AddResponse(
-        GURL(kImageUrl2), std::move(response_head), std::string(),
-        network::URLLoaderCompletionStatus(net::OK));
-
-    std::unique_ptr<DownloadResultFuture> result_future =
-        QueueNewJobWithFuture(kImageUrl2);
-    EXPECT_EQ(ScreensaverImageDownloadResult::kNetworkError,
-              result_future->Get<0>());
-    EXPECT_FALSE(result_future->Get<1>().has_value());
-  }
-
-  // Test a file save error result by deleting the destination folder before the
-  // URL request is solved.
-  {
-    std::unique_ptr<DownloadResultFuture> result_future =
-        QueueNewJobWithFuture(kImageUrl3);
-
-    // Wait until the request has been made to delete the tmp folder
-    url_loader_factory()->SetInterceptor(base::BindLambdaForTesting(
-        [&](const network::ResourceRequest& request) {
-          ASSERT_TRUE(request.url.is_valid());
-          EXPECT_EQ(kImageUrl3, request.url);
-
+  // Setup the fake URL responses:
+  //   * kImageUrl1 returns a valid response.
+  //   * kImageUrl2 returns a 404 error.
+  //   * kImageUrl3 deletes the download dir before returning a valid response.
+  url_loader_factory()->SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        ASSERT_TRUE(request.url.is_valid());
+        if (request.url == kImageUrl1) {
+          url_loader_factory()->AddResponse(kImageUrl1, kFileContents);
+        }
+        if (request.url == kImageUrl2) {
+          auto response_head = network::mojom::URLResponseHead::New();
+          response_head->headers =
+              base::MakeRefCounted<net::HttpResponseHeaders>("");
+          response_head->headers->SetHeader("Content-Type", "image/jpg");
+          response_head->headers->ReplaceStatusLine("HTTP/1.1 404 Not found");
+          url_loader_factory()->AddResponse(
+              GURL(kImageUrl2), std::move(response_head), std::string(),
+              network::URLLoaderCompletionStatus(net::OK));
+        }
+        if (request.url == kImageUrl3) {
           DeleteTestDownloadFolder();
           url_loader_factory()->AddResponse(kImageUrl3, kFileContents);
-        }));
-    EXPECT_EQ(ScreensaverImageDownloadResult::kFileSaveError,
-              result_future->Get<0>());
-    EXPECT_FALSE(result_future->Get<1>().has_value());
-  }
+        }
+      }));
+
+  // Test successful download.
+  std::vector<std::pair<base::FilePath, std::string>> expected_images;
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl1),
+                               std::string(kFileContents));
+
+  QueueNewImageDownload(kImageUrl1);
+  VerifySucessfulImageRequest(expected_images);
+
+  // Queue the request that should not download any file.
+  QueueNewImageDownload(kImageUrl2);
+  QueueNewImageDownload(kImageUrl3);
+
+  // Verify that the downloader did not create image files for the error
+  // downloads.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(base::PathExists(GetExpectedFilePath(kImageUrl2)));
+  EXPECT_FALSE(base::PathExists(GetExpectedFilePath(kImageUrl3)));
 }
 
 TEST_F(ScreensaverImageDownloaderTest, ReuseFilesInCacheTest) {
@@ -183,27 +183,18 @@
       }));
 
   // Test initial download.
-  {
-    std::unique_ptr<DownloadResultFuture> result_future =
-        QueueNewJobWithFuture(kImageUrl1);
-    EXPECT_EQ(ScreensaverImageDownloadResult::kSuccess,
-              result_future->Get<0>());
-    ASSERT_TRUE(result_future->Get<1>().has_value());
-    EXPECT_EQ(GetExpectedFilePath(kImageUrl1), result_future->Get<1>());
-    EXPECT_EQ(1u, urls_requested);
-  }
+  std::vector<std::pair<base::FilePath, std::string>> expected_images;
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl1),
+                               std::string(kFileContents));
+  QueueNewImageDownload(kImageUrl1);
+  VerifySucessfulImageRequest(expected_images);
+  EXPECT_EQ(1u, urls_requested);
 
   // Attempting to download the same URL should not create a new network
   // request.
-  {
-    std::unique_ptr<DownloadResultFuture> result_future =
-        QueueNewJobWithFuture(kImageUrl1);
-    EXPECT_EQ(ScreensaverImageDownloadResult::kSuccess,
-              result_future->Get<0>());
-    ASSERT_TRUE(result_future->Get<1>().has_value());
-    EXPECT_EQ(GetExpectedFilePath(kImageUrl1), result_future->Get<1>());
-    EXPECT_EQ(1u, urls_requested);
-  }
+  QueueNewImageDownload(kImageUrl1);
+  VerifySucessfulImageRequest(expected_images);
+  EXPECT_EQ(1u, urls_requested);
 
   url_loader_factory()->SetInterceptor(
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
@@ -212,23 +203,17 @@
       }));
 
   // A different URL should create a new network request.
-  {
-    std::unique_ptr<DownloadResultFuture> result_future =
-        QueueNewJobWithFuture(kImageUrl2);
-    EXPECT_EQ(ScreensaverImageDownloadResult::kSuccess,
-              result_future->Get<0>());
-    ASSERT_TRUE(result_future->Get<1>().has_value());
-    EXPECT_EQ(GetExpectedFilePath(kImageUrl2), result_future->Get<1>());
-    EXPECT_EQ(2u, urls_requested);
-  }
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl2),
+                               std::string(kFileContents));
+  QueueNewImageDownload(kImageUrl2);
+  VerifySucessfulImageRequest(expected_images);
+  EXPECT_EQ(2u, urls_requested);
 }
 
 TEST_F(ScreensaverImageDownloaderTest, VerifySerializedDownloadTest) {
   // Push two jobs and check the internal downloading queue
-  std::unique_ptr<DownloadResultFuture> result_future1 =
-      QueueNewJobWithFuture(kImageUrl1);
-  std::unique_ptr<DownloadResultFuture> result_future2 =
-      QueueNewJobWithFuture(kImageUrl2);
+  QueueNewImageDownload(kImageUrl1);
+  QueueNewImageDownload(kImageUrl2);
 
   // First job should be executing and expecting the URL response, verify that
   // the second job is in the queue
@@ -237,8 +222,11 @@
 
   // Resolve the first job
   url_loader_factory()->AddResponse(kImageUrl1, kFileContents);
-  VerifySucessfulImageRequest(std::move(result_future1), kImageUrl1,
-                              kFileContents);
+
+  std::vector<std::pair<base::FilePath, std::string>> expected_images;
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl1),
+                               std::string(kFileContents));
+  VerifySucessfulImageRequest(expected_images);
 
   // First job has been resolved, second job should be executing and expecting
   // the URL response.
@@ -246,24 +234,27 @@
   VerifyDownloadingQueueSize(0u);
 
   // Queue a third job while the second job is still waiting
-  std::unique_ptr<DownloadResultFuture> result_future3 =
-      QueueNewJobWithFuture(kImageUrl3);
+  QueueNewImageDownload(kImageUrl3);
 
   base::RunLoop().RunUntilIdle();
   VerifyDownloadingQueueSize(1u);
 
   // Resolve the second job
   url_loader_factory()->AddResponse(kImageUrl2, kFileContents);
-  VerifySucessfulImageRequest(std::move(result_future2), kImageUrl2,
-                              kFileContents);
+
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl2),
+                               std::string(kFileContents));
+  VerifySucessfulImageRequest(expected_images);
 
   base::RunLoop().RunUntilIdle();
   VerifyDownloadingQueueSize(0u);
 
   // Resolve the third job
   url_loader_factory()->AddResponse(kImageUrl3, kFileContents);
-  VerifySucessfulImageRequest(std::move(result_future3), kImageUrl3,
-                              kFileContents);
+
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl3),
+                               std::string(kFileContents));
+  VerifySucessfulImageRequest(expected_images);
 
   // Ensure that the queue remains empty
   base::RunLoop().RunUntilIdle();
@@ -274,10 +265,17 @@
   // Download two images to attempt clearing later.
   url_loader_factory()->AddResponse(kImageUrl1, kFileContents);
   url_loader_factory()->AddResponse(kImageUrl2, kFileContents);
-  VerifySucessfulImageRequest(QueueNewJobWithFuture(kImageUrl1), kImageUrl1,
-                              kFileContents);
-  VerifySucessfulImageRequest(QueueNewJobWithFuture(kImageUrl2), kImageUrl2,
-                              kFileContents);
+
+  std::vector<std::pair<base::FilePath, std::string>> expected_images;
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl1),
+                               std::string(kFileContents));
+  QueueNewImageDownload(kImageUrl1);
+  VerifySucessfulImageRequest(expected_images);
+
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl2),
+                               std::string(kFileContents));
+  QueueNewImageDownload(kImageUrl2);
+  VerifySucessfulImageRequest(expected_images);
 
   // Verify that images saved into disk are deleted properly.
   screensaver_image_downloader()->DeleteDownloadedImages();
@@ -288,12 +286,9 @@
 TEST_F(ScreensaverImageDownloaderTest, ClearRequestQueueTest) {
   // Queue 3 download request, the first one one will be executed, the latter
   // will be queued.
-  std::unique_ptr<DownloadResultFuture> result_future1 =
-      QueueNewJobWithFuture(kImageUrl1);
-  std::unique_ptr<DownloadResultFuture> result_future2 =
-      QueueNewJobWithFuture(kImageUrl2);
-  std::unique_ptr<DownloadResultFuture> result_future3 =
-      QueueNewJobWithFuture(kImageUrl3);
+  QueueNewImageDownload(kImageUrl1);
+  QueueNewImageDownload(kImageUrl2);
+  QueueNewImageDownload(kImageUrl3);
 
   base::RunLoop().RunUntilIdle();
   VerifyDownloadingQueueSize(2u);
@@ -303,17 +298,16 @@
   screensaver_image_downloader()->ClearRequestQueue();
 
   // Verify that the pending request was executed until completion.
-  VerifySucessfulImageRequest(std::move(result_future1), kImageUrl1,
-                              kFileContents);
+  std::vector<std::pair<base::FilePath, std::string>> expected_images;
+  expected_images.emplace_back(GetExpectedFilePath(kImageUrl1),
+                               std::string(kFileContents));
+  VerifySucessfulImageRequest(expected_images);
 
-  // Verify that the other requests were notified of them being cancelled.
-  EXPECT_EQ(ScreensaverImageDownloadResult::kCancelled,
-            result_future2->Get<0>());
-  EXPECT_FALSE(result_future2->Get<1>().has_value());
-
-  EXPECT_EQ(ScreensaverImageDownloadResult::kCancelled,
-            result_future3->Get<0>());
-  EXPECT_FALSE(result_future3->Get<1>().has_value());
+  // Verify that the downloader did not create image files for the cancelled
+  // downloads.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(base::PathExists(GetExpectedFilePath(kImageUrl2)));
+  EXPECT_FALSE(base::PathExists(GetExpectedFilePath(kImageUrl3)));
 }
 
 }  // namespace ash
diff --git a/ash/ambient/managed/screensaver_images_policy_handler.cc b/ash/ambient/managed/screensaver_images_policy_handler.cc
index c180f12..801be40a 100644
--- a/ash/ambient/managed/screensaver_images_policy_handler.cc
+++ b/ash/ambient/managed/screensaver_images_policy_handler.cc
@@ -64,7 +64,10 @@
 
   AmbientClient& ambient_client = CHECK_DEREF(AmbientClient::Get());
   image_downloader_ = std::make_unique<ScreensaverImageDownloader>(
-      ambient_client.GetURLLoaderFactory(), GetDownloaderRootPath());
+      ambient_client.GetURLLoaderFactory(), GetDownloaderRootPath(),
+      base::BindRepeating(
+          &ScreensaverImagesPolicyHandler::OnDownloadedImageListUpdated,
+          base::Unretained(this)));
 
   pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
   pref_change_registrar_->Init(pref_service);
@@ -87,9 +90,6 @@
     return;
   }
 
-  // TODO(b/278857721): Do not download/cache images if the ScreensaverEnabled
-  // pref is false.
-
   const base::Value::List& url_list = user_pref_service_->GetList(
       ash::ambient::prefs::kAmbientModeManagedScreensaverImages);
   if (url_list.empty()) {
@@ -117,27 +117,16 @@
       LOG(WARNING) << "Ignored invalid URL: " << url;
       continue;
     }
-    auto job = std::make_unique<ScreensaverImageDownloader::Job>(
-        url.spec(),
-        base::BindOnce(&ScreensaverImagesPolicyHandler::OnDownloadJobCompleted,
-                       weak_ptr_factory_.GetWeakPtr()));
+    auto job = std::make_unique<ScreensaverImageDownloader::Job>(url.spec());
 
     image_downloader_->QueueDownloadJob(std::move(job));
   }
 }
 
-void ScreensaverImagesPolicyHandler::OnDownloadJobCompleted(
-    ScreensaverImageDownloadResult result,
-    absl::optional<base::FilePath> path) {
-  if (result != ScreensaverImageDownloadResult::kSuccess) {
-    return;
-  }
-  CHECK(path.has_value());
-  downloaded_images_.insert(*path);
-
+void ScreensaverImagesPolicyHandler::OnDownloadedImageListUpdated(
+    const std::vector<base::FilePath>& images) {
   if (on_images_updated_callback_) {
-    on_images_updated_callback_.Run(std::vector<base::FilePath>(
-        downloaded_images_.begin(), downloaded_images_.end()));
+    on_images_updated_callback_.Run(images);
   }
 }
 
@@ -149,17 +138,15 @@
 
 void ScreensaverImagesPolicyHandler::SetImagesForTesting(
     const std::vector<base::FilePath>& images_file_paths) {
-  downloaded_images_ = base::flat_set<base::FilePath>(images_file_paths);
-  if (on_images_updated_callback_) {
-    on_images_updated_callback_.Run(std::vector<base::FilePath>(
-        downloaded_images_.begin(), downloaded_images_.end()));
-  }
+  image_downloader_->SetImagesForTesting(images_file_paths);  // IN-TEST
 }
 
 std::vector<base::FilePath>
 ScreensaverImagesPolicyHandler::GetScreensaverImages() {
-  return std::vector<base::FilePath>(downloaded_images_.begin(),
-                                     downloaded_images_.end());
+  if (image_downloader_) {
+    return image_downloader_->GetScreensaverImages();
+  }
+  return std::vector<base::FilePath>();
 }
 
 }  // namespace ash
diff --git a/ash/ambient/managed/screensaver_images_policy_handler.h b/ash/ambient/managed/screensaver_images_policy_handler.h
index aeeee47..9c34715b 100644
--- a/ash/ambient/managed/screensaver_images_policy_handler.h
+++ b/ash/ambient/managed/screensaver_images_policy_handler.h
@@ -10,7 +10,6 @@
 #include "ash/ambient/managed/screensaver_image_downloader.h"
 #include "ash/ash_export.h"
 #include "ash/public/cpp/ambient/ambient_managed_photo_source.h"
-#include "base/containers/flat_set.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
@@ -45,11 +44,7 @@
 
   void OnAmbientModeManagedScreensaverImagesPrefChanged();
 
-  // Download completion handler.
-  void OnDownloadJobCompleted(ScreensaverImageDownloadResult result,
-                              absl::optional<base::FilePath> path);
-
-  base::flat_set<base::FilePath> downloaded_images_;
+  void OnDownloadedImageListUpdated(const std::vector<base::FilePath>& images);
 
   raw_ptr<PrefService> user_pref_service_ = nullptr;
 
diff --git a/ash/ambient/managed/screensaver_images_policy_handler_unittest.cc b/ash/ambient/managed/screensaver_images_policy_handler_unittest.cc
index 17cf3e7..2d15115 100644
--- a/ash/ambient/managed/screensaver_images_policy_handler_unittest.cc
+++ b/ash/ambient/managed/screensaver_images_policy_handler_unittest.cc
@@ -77,10 +77,10 @@
     AshTestBase::TearDown();
   }
 
-  void TriggerOnDownloadJobCompleted(ScreensaverImageDownloadResult result,
-                                     absl::optional<base::FilePath> path) {
+  void TriggerOnDownloadedImageListUpdated(
+      const std::vector<base::FilePath>& image_list) {
     ASSERT_TRUE(policy_handler());
-    policy_handler_->OnDownloadJobCompleted(result, path);
+    policy_handler_->OnDownloadedImageListUpdated(image_list);
   }
 
   ScreensaverImageDownloader* GetPrivateImageDownloader(
@@ -191,8 +191,7 @@
   // Expect callbacks when images are downloaded.
   base::FilePath file_path1(kFakeFilePath1);
   {
-    TriggerOnDownloadJobCompleted(ScreensaverImageDownloadResult::kSuccess,
-                                  file_path1);
+    TriggerOnDownloadedImageListUpdated({file_path1});
     EXPECT_TRUE(test_future.Wait());
     std::vector<base::FilePath> file_paths = test_future.Take();
     ASSERT_EQ(1u, file_paths.size());
@@ -200,8 +199,7 @@
   }
   base::FilePath file_path2(kFakeFilePath2);
   {
-    TriggerOnDownloadJobCompleted(ScreensaverImageDownloadResult::kSuccess,
-                                  file_path2);
+    TriggerOnDownloadedImageListUpdated({file_path1, file_path2});
     EXPECT_TRUE(test_future.Wait());
     std::vector<base::FilePath> file_paths = test_future.Take();
     ASSERT_EQ(2u, file_paths.size());
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 3302ae2..b048837 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -24,7 +24,6 @@
 #include "ash/ambient/ui/ambient_info_view.h"
 #include "ash/ambient/ui/ambient_slideshow_peripheral_ui.h"
 #include "ash/ambient/ui/ambient_view_ids.h"
-#include "ash/ambient/ui/jitter_calculator.h"
 #include "ash/ambient/ui/media_string_view.h"
 #include "ash/ambient/ui/photo_view.h"
 #include "ash/constants/ash_features.h"
@@ -42,22 +41,17 @@
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/location.h"
-#include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/sequenced_task_runner.h"
-#include "base/test/bind.h"
 #include "base/test/scoped_run_loop_timeout.h"
-#include "base/threading/scoped_blocking_call.h"
 #include "base/time/time.h"
-#include "base/values.h"
-#include "chromeos/ash/components/login/auth/auth_events_recorder.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/power_manager/idle.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/codec/jpeg_codec.h"
 #include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/image/image_unittest_util.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
 
@@ -456,6 +450,7 @@
 
 void AmbientAshTestBase::FastForwardByLockScreenInactivityTimeout(
     float factor) {
+  DCHECK_GT(factor, 0.f);
   task_environment()->FastForwardBy(factor *
                                     ambient_controller()
                                         ->ambient_ui_model()
@@ -468,6 +463,17 @@
       ambient_controller()->ambient_ui_model()->photo_refresh_interval());
 }
 
+absl::optional<float>
+AmbientAshTestBase::GetRemainingLockScreenTimeoutFraction() {
+  const auto& inactivity_timer = ambient_controller()->inactivity_timer_;
+  if (!inactivity_timer.IsRunning()) {
+    return absl::nullopt;
+  }
+
+  return (inactivity_timer.desired_run_time() - base::TimeTicks::Now()) /
+         inactivity_timer.GetCurrentDelay();
+}
+
 void AmbientAshTestBase::FastForwardTiny() {
   // `TestAmbientURLLoaderImpl` has a small delay (1ms) to fake download delay,
   // here we fake plenty of time to download the image.
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index 21364ddd..12a6ecbd 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -24,6 +24,7 @@
 #include "base/time/time.h"
 #include "chromeos/ash/components/login/auth/auth_events_recorder.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
@@ -147,6 +148,11 @@
   void FastForwardByLockScreenInactivityTimeout(
       float factor = kDefaultFastForwardFactor);
 
+  // Approximately how much of the lock screen inactivity timeout is left.
+  // Bounded to [0,1], 1 meaning that the timer just started. If the lock screen
+  // inactivity timer is not running, returns null.
+  absl::optional<float> GetRemainingLockScreenTimeoutFraction();
+
   // Advance the task environment timer to load the next photo, scaled by
   // `factor`.
   void FastForwardByPhotoRefreshInterval(
diff --git a/ash/ambient/test/test_ambient_client.cc b/ash/ambient/test/test_ambient_client.cc
index ef4c9a1..8481985 100644
--- a/ash/ambient/test/test_ambient_client.cc
+++ b/ash/ambient/test/test_ambient_client.cc
@@ -99,6 +99,11 @@
   return url_loader_factory_;
 }
 
+scoped_refptr<network::SharedURLLoaderFactory>
+TestAmbientClient::GetSigninURLLoaderFactory() {
+  return url_loader_factory_;
+}
+
 void TestAmbientClient::RequestWakeLockProvider(
     mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) {
   wake_lock_provider_->BindReceiver(std::move(receiver));
diff --git a/ash/ambient/test/test_ambient_client.h b/ash/ambient/test/test_ambient_client.h
index 04d37a5..a597982e 100644
--- a/ash/ambient/test/test_ambient_client.h
+++ b/ash/ambient/test/test_ambient_client.h
@@ -34,6 +34,9 @@
   void DownloadImage(const std::string& url,
                      ash::ImageDownloader::DownloadCallback callback) override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
+  scoped_refptr<network::SharedURLLoaderFactory> GetSigninURLLoaderFactory()
+      override;
+
   void RequestWakeLockProvider(
       mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) override;
   bool ShouldUseProdServer() override;
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index f485905..fbc100b 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1107,6 +1107,9 @@
       <message name="IDS_ASH_AUTOCLICK_SCROLL_CLOSE" desc="The tooltip text for the automatic clicks menu option that results in the scrolling menu being closed.">
         Close
       </message>
+      <message name="IDS_ASH_AUTOCLICK_SCROLL_BUBBLE" desc="The name of a window showing autoclick scroll controls." is_accessibility_with_no_ui="true">
+        Scroll controls
+      </message>
       <message name="IDS_ASH_AUTOCLICK_DISABLE_CONFIRMATION_TEXT" desc="The text for the modal dialog shown when the user disables automatic clicks, to confirm they meant to disable the feature.">
         Are you sure you want to turn off automatic clicks?
       </message>
diff --git a/ash/assistant/assistant_controller_impl.cc b/ash/assistant/assistant_controller_impl.cc
index b824f49..43250ef 100644
--- a/ash/assistant/assistant_controller_impl.cc
+++ b/ash/assistant/assistant_controller_impl.cc
@@ -45,6 +45,7 @@
 }  // namespace
 
 AssistantControllerImpl::AssistantControllerImpl() {
+  Shell::Get()->AddShellObserver(this);
   assistant_state_controller_.AddObserver(this);
   CrasAudioHandler::Get()->AddAudioObserver(this);
   AddObserver(this);
@@ -59,14 +60,7 @@
   NotifyConstructed();
 }
 
-AssistantControllerImpl::~AssistantControllerImpl() {
-  NotifyDestroying();
-
-  CrasAudioHandler::Get()->RemoveAudioObserver(this);
-  Shell::Get()->accessibility_controller()->RemoveObserver(this);
-  assistant_state_controller_.RemoveObserver(this);
-  RemoveObserver(this);
-}
+AssistantControllerImpl::~AssistantControllerImpl() = default;
 
 // static
 void AssistantControllerImpl::RegisterProfilePrefs(
@@ -304,6 +298,15 @@
   assistant_->OnColorModeChanged(dark_mode_enabled);
 }
 
+void AssistantControllerImpl::OnShellDestroying() {
+  NotifyDestroying();
+  CrasAudioHandler::Get()->RemoveAudioObserver(this);
+  Shell::Get()->accessibility_controller()->RemoveObserver(this);
+  assistant_state_controller_.RemoveObserver(this);
+  Shell::Get()->RemoveShellObserver(this);
+  RemoveObserver(this);
+}
+
 bool AssistantControllerImpl::IsAssistantReady() const {
   if (!assistant_) {
     return false;
diff --git a/ash/assistant/assistant_controller_impl.h b/ash/assistant/assistant_controller_impl.h
index 5102f98..74ef556e 100644
--- a/ash/assistant/assistant_controller_impl.h
+++ b/ash/assistant/assistant_controller_impl.h
@@ -28,6 +28,7 @@
 #include "ash/public/cpp/image_downloader.h"
 #include "ash/public/cpp/style/color_mode_observer.h"
 #include "ash/public/mojom/assistant_volume_control.mojom.h"
+#include "ash/shell_observer.h"
 #include "ash/style/dark_light_mode_controller_impl.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -55,7 +56,8 @@
       public CrasAudioHandler::AudioObserver,
       public AccessibilityObserver,
       public AssistantInterfaceBinder,
-      public ColorModeObserver {
+      public ColorModeObserver,
+      public ShellObserver {
  public:
   AssistantControllerImpl();
 
@@ -108,6 +110,9 @@
   // ColorModeObserver:
   void OnColorModeChanged(bool dark_mode_enabled) override;
 
+  // ShellObserver:
+  void OnShellDestroying() override;
+
   AssistantAlarmTimerControllerImpl* alarm_timer_controller() {
     return &assistant_alarm_timer_controller_;
   }
diff --git a/ash/booting/booting_animation_controller.cc b/ash/booting/booting_animation_controller.cc
index b3e46ae..99cf79d 100644
--- a/ash/booting/booting_animation_controller.cc
+++ b/ash/booting/booting_animation_controller.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "ash/booting/booting_animation_view.h"
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
 #include "base/files/file_util.h"
@@ -35,6 +36,9 @@
 }  // namespace
 
 BootingAnimationController::BootingAnimationController() {
+  CHECK(ash::Shell::Get()->display_configurator());
+  scoped_display_configurator_observer_.Observe(
+      ash::Shell::Get()->display_configurator());
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE,
       {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
@@ -67,14 +71,59 @@
   widget_->Init(std::move(params));
 
   if (animation_data_.empty()) {
+    LOG(ERROR) << "Booting animation isn't ready yet.";
     start_once_ready_ = true;
     return;
   }
   StartAnimation();
 }
 
+void BootingAnimationController::ShowAnimationWithEndCallback(
+    base::OnceClosure callback) {
+  animation_played_callback_ = std::move(callback);
+
+  if (!scoped_display_configurator_observer_.IsObserving()) {
+    Show();
+  }
+}
+
 void BootingAnimationController::Finish() {
   widget_.reset();
+  animation_played_callback_.Reset();
+}
+
+base::WeakPtr<BootingAnimationController>
+BootingAnimationController::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+void BootingAnimationController::OnDisplayModeChanged(
+    const display::DisplayConfigurator::DisplayStateList& displays) {
+  if (!is_gpu_ready_) {
+    return;
+  }
+
+  scoped_display_configurator_observer_.Reset();
+  if (!animation_played_callback_.is_null()) {
+    Show();
+  }
+}
+
+void BootingAnimationController::OnDisplaySnapshotsInvalidated() {
+  // This call represents that GPU has returned us valid display snapshots, but
+  // they are not still applied. Starting the animation before modeset happens
+  // is too early and we need to wait for the next `OnDisplayModeChanged` call.
+  is_gpu_ready_ = true;
+}
+
+void BootingAnimationController::AnimationCycleEnded(
+    const lottie::Animation* animation) {
+  // Once animation has finished playing we might delete it. Stop observation
+  // here explicitly.
+  scoped_animation_observer_.Reset();
+  if (!animation_played_callback_.is_null()) {
+    std::move(animation_played_callback_).Run();
+  }
 }
 
 void BootingAnimationController::OnAnimationDataFetched(std::string data) {
@@ -91,9 +140,17 @@
 }
 
 void BootingAnimationController::StartAnimation() {
+  CHECK(!animation_played_callback_.is_null() && is_gpu_ready_);
+  if (was_shown_) {
+    return;
+  }
+
+  was_shown_ = true;
   start_once_ready_ = false;
   BootingAnimationView* view = widget_->SetContentsView(
       std::make_unique<BootingAnimationView>(animation_data_));
+  // Observe animation to know when it finishes playing.
+  scoped_animation_observer_.Observe(view->GetAnimatedImage());
   widget_->Show();
   view->Play();
 }
diff --git a/ash/booting/booting_animation_controller.h b/ash/booting/booting_animation_controller.h
index 3addc45..206d8a8 100644
--- a/ash/booting/booting_animation_controller.h
+++ b/ash/booting/booting_animation_controller.h
@@ -6,33 +6,63 @@
 #define ASH_BOOTING_BOOTING_ANIMATION_CONTROLLER_H_
 
 #include "ash/ash_export.h"
+#include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "ui/display/manager/display_configurator.h"
+#include "ui/lottie/animation_observer.h"
 #include "ui/views/widget/unique_widget_ptr.h"
 
 namespace ash {
 
-class ASH_EXPORT BootingAnimationController {
+class ASH_EXPORT BootingAnimationController
+    : public display::DisplayConfigurator::Observer,
+      public lottie::AnimationObserver {
  public:
   BootingAnimationController();
   BootingAnimationController(const BootingAnimationController&) = delete;
   BootingAnimationController& operator=(const BootingAnimationController&) =
       delete;
-  ~BootingAnimationController();
+  ~BootingAnimationController() override;
 
-  // Shows the widget and starts to play a booting animation.
-  void Show();
+  // Sets the `animation_played_callback_` that is fired when the animation
+  // finishes playing. Starts the animation if the GPU is ready, otherwise
+  // waits for it.
+  void ShowAnimationWithEndCallback(base::OnceClosure callback);
 
   // Cleans up the animation, resets the widget and the view.
   void Finish();
 
+  base::WeakPtr<BootingAnimationController> GetWeakPtr();
+
  private:
+  // display::DisplayConfigurator::Observer:
+  void OnDisplayModeChanged(
+      const display::DisplayConfigurator::DisplayStateList& displays) override;
+  void OnDisplaySnapshotsInvalidated() override;
+
+  // lottie::AnimationObserver:
+  void AnimationCycleEnded(const lottie::Animation* animation) override;
+
+  // Shows the widget and starts to play a booting animation.
+  void Show();
   void OnAnimationDataFetched(std::string data);
   void StartAnimation();
 
   std::string animation_data_;
   views::UniqueWidgetPtr widget_;
   bool start_once_ready_ = false;
+  bool was_shown_ = false;
+  bool is_gpu_ready_ = false;
+  base::OnceClosure animation_played_callback_;
+
+  base::ScopedObservation<display::DisplayConfigurator,
+                          display::DisplayConfigurator::Observer>
+      scoped_display_configurator_observer_{this};
+
+  base::ScopedObservation<lottie::Animation, lottie::AnimationObserver>
+      scoped_animation_observer_{this};
 
   base::WeakPtrFactory<BootingAnimationController> weak_factory_{this};
 };
diff --git a/ash/booting/booting_animation_view.cc b/ash/booting/booting_animation_view.cc
index 0ee7767..263c3f1a 100644
--- a/ash/booting/booting_animation_view.cc
+++ b/ash/booting/booting_animation_view.cc
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/image_util.h"
 #include "cc/paint/skottie_wrapper.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/geometry/size.h"
 #include "ui/lottie/animation.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/animated_image_view.h"
@@ -76,6 +77,10 @@
       lottie::Animation::Style::kLinear, *animation_->animated_image()));
 }
 
+lottie::Animation* BootingAnimationView::GetAnimatedImage() {
+  return animation_->animated_image();
+}
+
 void BootingAnimationView::OnViewBoundsChanged(View* observed_view) {
   gfx::Rect content_bounds = observed_view->GetContentsBounds();
   if (content_bounds.IsEmpty()) {
diff --git a/ash/booting/booting_animation_view.h b/ash/booting/booting_animation_view.h
index 724a2d1..4a76e68 100644
--- a/ash/booting/booting_animation_view.h
+++ b/ash/booting/booting_animation_view.h
@@ -28,6 +28,8 @@
 
   void Play();
 
+  lottie::Animation* GetAnimatedImage();
+
  private:
   // views::ViewObserver:
   void OnViewBoundsChanged(View* observed_view) override;
diff --git a/ash/capture_mode/capture_mode_behavior.cc b/ash/capture_mode/capture_mode_behavior.cc
index cdf46ca..c19ffa4 100644
--- a/ash/capture_mode/capture_mode_behavior.cc
+++ b/ash/capture_mode/capture_mode_behavior.cc
@@ -8,11 +8,14 @@
 #include <utility>
 #include <vector>
 
+#include "ash/capture_mode/capture_mode_constants.h"
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_metrics.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/constants/ash_features.h"
 #include "ash/projector/projector_controller_impl.h"
+#include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_layout_manager.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
@@ -23,6 +26,15 @@
 
 namespace {
 
+// Full size of capture mode bar view, the width of which will be
+// adjusted based on the current active behavior.
+constexpr gfx::Size kFullBarSize{376, 64};
+
+// Distance from the bottom of the capture bar to the bottom of the display, top
+// of the hotseat or top of the shelf depending on the shelf alignment or
+// hotseat visibility.
+constexpr int kDistanceFromShelfOrHotseatTopDp = 16;
+
 // Returns the current configs before been overwritten by the client-initiated
 // capture mode session
 CaptureModeSessionConfigs GetCaptureModeSessionConfigs() {
@@ -122,6 +134,12 @@
   }
   const char* GetClientMetricComponent() const override { return "Projector."; }
 
+ protected:
+  int GetCaptureBarWidth() const override {
+    return kFullBarSize.width() - capture_mode::kButtonSize.width() -
+           capture_mode::kSpaceBetweenCaptureModeTypeButtons;
+  }
+
  private:
   // Called when the Projector controller creates the DriveFS folder that will
   // host the video file along with the associated metadata file created by the
@@ -285,4 +303,34 @@
   return "";
 }
 
+gfx::Rect CaptureModeBehavior::GetCaptureBarBounds(aura::Window* root) const {
+  DCHECK(root);
+
+  auto bounds = root->GetBoundsInScreen();
+  int bar_y = bounds.bottom();
+  Shelf* shelf = Shelf::ForWindow(root);
+  if (shelf->IsHorizontalAlignment()) {
+    // Get the widget which has the shelf icons. This is the hotseat widget if
+    // the hotseat is extended, shelf widget otherwise.
+    const bool hotseat_extended =
+        shelf->shelf_layout_manager()->hotseat_state() ==
+        HotseatState::kExtended;
+    views::Widget* shelf_widget =
+        hotseat_extended ? static_cast<views::Widget*>(shelf->hotseat_widget())
+                         : static_cast<views::Widget*>(shelf->shelf_widget());
+    bar_y = shelf_widget->GetWindowBoundsInScreen().y();
+  }
+
+  gfx::Size bar_size = kFullBarSize;
+  bar_size.set_width(GetCaptureBarWidth());
+  bar_y -= (kDistanceFromShelfOrHotseatTopDp + bar_size.height());
+  bounds.ClampToCenteredSize(bar_size);
+  bounds.set_y(bar_y);
+  return bounds;
+}
+
+int CaptureModeBehavior::GetCaptureBarWidth() const {
+  return kFullBarSize.width();
+}
+
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_behavior.h b/ash/capture_mode/capture_mode_behavior.h
index 9d22108..85b47287 100644
--- a/ash/capture_mode/capture_mode_behavior.h
+++ b/ash/capture_mode/capture_mode_behavior.h
@@ -13,6 +13,14 @@
 #include "base/functional/callback_helpers.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+namespace aura {
+class Window;
+}  // namespace aura
+
+namespace gfx {
+class Rect;
+}  // namespace gfx
+
 namespace ash {
 
 // Contains the cached capture mode configurations that will be used for
@@ -81,9 +89,18 @@
   // indicate the histogram is for a projector-initiated capture mode session.
   virtual const char* GetClientMetricComponent() const;
 
+  // Gets the bounds in screen coordinates of the capture bar in the given
+  // `root` window. The returned bounds of the bar will vary depending on the
+  // actual type of the behavior.
+  gfx::Rect GetCaptureBarBounds(aura::Window* root) const;
+
  protected:
   explicit CaptureModeBehavior(const CaptureModeSessionConfigs& configs);
 
+  // Called by `GetCaptureBarBounds` to adjust the width of the bar on different
+  // types of behavior.
+  virtual int GetCaptureBarWidth() const;
+
   // Capture mode session configs to be used for the current capture mode
   // session.
   CaptureModeSessionConfigs capture_mode_configs_;
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index 3e12e48..502abde 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -496,8 +496,7 @@
   ClampCaptureRegionToRootWindowSize();
 
   capture_mode_bar_widget_->Init(CreateWidgetParams(
-      parent,
-      capture_mode_util::GetCaptureBarBounds(current_root_, active_behavior_),
+      parent, active_behavior_->GetCaptureBarBounds(current_root_),
       "CaptureModeBarWidget"));
   capture_mode_bar_view_ = capture_mode_bar_widget_->SetContentsView(
       std::make_unique<NormalCaptureBarView>(active_behavior_));
@@ -1588,7 +1587,7 @@
   // The sequence matters here since settings bounds depend on capture bar
   // bounds.
   capture_mode_bar_widget_->SetBounds(
-      capture_mode_util::GetCaptureBarBounds(current_root_, active_behavior_));
+      active_behavior_->GetCaptureBarBounds(current_root_));
   MaybeUpdateSettingsBounds();
   if (user_nudge_controller_)
     user_nudge_controller_->Reposition();
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index f04926f..6f466ba 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -16,8 +16,6 @@
 #include "ash/public/cpp/window_finder.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
-#include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
@@ -51,15 +49,6 @@
 constexpr int kBannerViewBottomRadius = 8;
 constexpr float kScaleUpFactor = 0.8f;
 
-// Full size of capture mode bar view, the width of which will be
-// adjusted according to the provided active behavior.
-constexpr gfx::Size kFullBarSize{376, 64};
-
-// Distance from the bottom of the capture bar to the bottom of the display, top
-// of the hotseat or top of the shelf depending on the shelf alignment or
-// hotseat visibility.
-constexpr int kDistanceFromShelfOrHotseatTopDp = 16;
-
 // The app ID used for the capture mode privacy indicators.
 constexpr char kCaptureModePrivacyIndicatorsId[] = "system-capture-mode";
 
@@ -566,36 +555,4 @@
       std::make_unique<views::HighlightBorder>(corner_radius, type));
 }
 
-gfx::Rect GetCaptureBarBounds(aura::Window* root,
-                              CaptureModeBehavior* active_behavior) {
-  DCHECK(root);
-
-  auto bounds = root->GetBoundsInScreen();
-  int bar_y = bounds.bottom();
-  Shelf* shelf = Shelf::ForWindow(root);
-  if (shelf->IsHorizontalAlignment()) {
-    // Get the widget which has the shelf icons. This is the hotseat widget if
-    // the hotseat is extended, shelf widget otherwise.
-    const bool hotseat_extended =
-        shelf->shelf_layout_manager()->hotseat_state() ==
-        HotseatState::kExtended;
-    views::Widget* shelf_widget =
-        hotseat_extended ? static_cast<views::Widget*>(shelf->hotseat_widget())
-                         : static_cast<views::Widget*>(shelf->shelf_widget());
-    bar_y = shelf_widget->GetWindowBoundsInScreen().y();
-  }
-
-  gfx::Size bar_size = kFullBarSize;
-  CHECK(active_behavior);
-  if (!active_behavior->ShouldImageCaptureTypeBeAllowed()) {
-    bar_size.set_width(kFullBarSize.width() -
-                       capture_mode::kButtonSize.width() -
-                       capture_mode::kSpaceBetweenCaptureModeTypeButtons);
-  }
-  bar_y -= (kDistanceFromShelfOrHotseatTopDp + bar_size.height());
-  bounds.ClampToCenteredSize(bar_size);
-  bounds.set_y(bar_y);
-  return bounds;
-}
-
 }  // namespace ash::capture_mode_util
diff --git a/ash/capture_mode/capture_mode_util.h b/ash/capture_mode/capture_mode_util.h
index 1ab410f..057c419 100644
--- a/ash/capture_mode/capture_mode_util.h
+++ b/ash/capture_mode/capture_mode_util.h
@@ -42,7 +42,6 @@
 
 namespace ash {
 
-class CaptureModeBehavior;
 class StopRecordingButtonTray;
 
 namespace capture_mode_util {
@@ -212,11 +211,6 @@
                         int corner_radius,
                         views::HighlightBorder::Type type);
 
-// Gets the bounds in screen coordinates of the capture bar in the given `root`
-// window. Its width will be adjusted based on the `active_behavior`.
-gfx::Rect GetCaptureBarBounds(aura::Window* root,
-                              CaptureModeBehavior* active_behavior);
-
 }  // namespace capture_mode_util
 
 }  // namespace ash
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index c4d75e9..7b02b81 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -811,7 +811,7 @@
 // effectively matching version 2 behavior more closely.
 BASE_FEATURE(kExoLinuxDmabufModifiers,
              "ExoLinuxDmabufModifiers",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Enable or disable use of ordinal (unaccelerated) motion by Exo clients.
 BASE_FEATURE(kExoOrdinalMotion,
@@ -1313,15 +1313,26 @@
              base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables or disables the internal server side speech recognition on ChromeOS.
+// Controls the launched locales.
 BASE_FEATURE(kInternalServerSideSpeechRecognition,
              "InternalServerSideSpeechRecognition",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Feature overrides the `InternalServerSideSpeechRecognition` flag if disabled.
+// Feature overrides the `InternalServerSideSpeechRecognition` that is exposed
+// via chrome://flags. This flag is used as a kill switch to disable the feature
+// in case that the feature introduced unexpected server load.
+// TODO(b/265957535) Clean up this flag after launch.
 BASE_FEATURE(kInternalServerSideSpeechRecognitionControl,
              "InternalServerSideSpeechRecognitionControl",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables or disables the internal server side speech recognition on ChromeOS.
+// The supported locales for this feature are specified using the locales
+// filter in finch config.
+BASE_FEATURE(kInternalServerSideSpeechRecognitionByFinch,
+             "InternalServerSideSpeechRecognitionByFinch",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables sending `client-info` values to IPP printers on ChromeOS.
 BASE_FEATURE(kIppClientInfo, "IppClientInfo", base::FEATURE_ENABLED_BY_DEFAULT);
 
@@ -1650,7 +1661,7 @@
 // jelly-colors flag to also be enabled.
 BASE_FEATURE(kOsFeedbackJelly,
              "OsFeedbackJelly",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables app badging toggle to be displayed in app notification page in
 // ChromeOS Settings.
@@ -2240,6 +2251,11 @@
 BASE_FEATURE(kVideoConference,
              "VideoConference",
              base::FEATURE_DISABLED_BY_DEFAULT);
+// Restricts the video conference feature to the intended
+// target population,
+BASE_FEATURE(kFeatureManagementVideoConference,
+             "FeatureManagementVideoConference",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Controls whether the vc background replace is enabled.
 BASE_FEATURE(kVcBackgroundReplace,
@@ -2936,6 +2952,15 @@
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 }
 
+bool IsInternalServerSideSpeechRecognitionEnabledByFinch() {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  return base::FeatureList::IsEnabled(
+      kInternalServerSideSpeechRecognitionByFinch);
+#else
+  return false;
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+}
+
 bool IsIppClientInfoEnabled() {
   return base::FeatureList::IsEnabled(kIppClientInfo);
 }
@@ -3473,7 +3498,8 @@
 }
 
 bool IsVideoConferenceEnabled() {
-  return base::FeatureList::IsEnabled(kVideoConference) &&
+  return (base::FeatureList::IsEnabled(kVideoConference) ||
+          base::FeatureList::IsEnabled(kFeatureManagementVideoConference)) &&
          switches::IsCameraEffectsSupportedByHardware();
 }
 
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index e88ce7f..879a237 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -404,6 +404,8 @@
 BASE_DECLARE_FEATURE(kInternalServerSideSpeechRecognition);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kInternalServerSideSpeechRecognitionControl);
+COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kInternalServerSideSpeechRecognitionByFinch);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kIppClientInfo);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kJapaneseFunctionRow);
@@ -648,6 +650,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kVcSegmentationModel);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kVideoConference);
 COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kFeatureManagementVideoConference);
+COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kVirtualKeyboardBorderedKey);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kVirtualKeyboardMultitouch);
@@ -807,6 +811,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsInternalServerSideSpeechRecognitionEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
+bool IsInternalServerSideSpeechRecognitionEnabledByFinch();
+COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsInternalServerSideSpeechRecognitionControlEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsIppClientInfoEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForDiagnosticsApp();
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index ca8d0d4..8d4262e 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -1010,6 +1010,11 @@
 // A boolean pref indicating whether the microphone is allowed to be used.
 const char kUserMicrophoneAllowed[] = "ash.user.microphone_allowed";
 
+// A boolean pref indicating whether a user has enabled the speak-on-mute
+// detection.
+const char kUserSpeakOnMuteDetectionEnabled[] =
+    "ash.user.speak_on_mute_detection_enabled";
+
 // A boolean pref indicating whether the geolocation is allowed for the user.
 const char kUserGeolocationAllowed[] = "ash.user.geolocation_allowed";
 // An enum pref indicating whether the geolocation is allowed outside user
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 97205814..c57e9e0 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -467,6 +467,8 @@
 
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kUserCameraAllowed[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kUserMicrophoneAllowed[];
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kUserSpeakOnMuteDetectionEnabled[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kUserGeolocationAllowed[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kDeviceGeolocationAllowed[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
diff --git a/ash/drag_drop/tab_drag_drop_delegate.h b/ash/drag_drop/tab_drag_drop_delegate.h
index 6336ce7d..2f20f75 100644
--- a/ash/drag_drop/tab_drag_drop_delegate.h
+++ b/ash/drag_drop/tab_drag_drop_delegate.h
@@ -11,6 +11,7 @@
 #include "ash/drag_drop/drag_drop_capture_delegate.h"
 #include "ash/drag_drop/tab_drag_drop_windows_hider.h"
 #include "ash/wm/splitview/split_view_controller.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "ui/aura/window_observer.h"
 #include "ui/gfx/geometry/point.h"
diff --git a/ash/glanceables/classroom/glanceables_classroom_client.h b/ash/glanceables/classroom/glanceables_classroom_client.h
new file mode 100644
index 0000000..3dad86fe
--- /dev/null
+++ b/ash/glanceables/classroom/glanceables_classroom_client.h
@@ -0,0 +1,17 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_GLANCEABLES_CLASSROOM_GLANCEABLES_CLASSROOM_CLIENT_H_
+#define ASH_GLANCEABLES_CLASSROOM_GLANCEABLES_CLASSROOM_CLIENT_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+// Interface for the classroom browser client.
+class ASH_EXPORT GlanceablesClassroomClient {};
+
+}  // namespace ash
+
+#endif  // ASH_GLANCEABLES_CLASSROOM_GLANCEABLES_CLASSROOM_CLIENT_H_
diff --git a/ash/glanceables/glanceables_v2_controller.cc b/ash/glanceables/glanceables_v2_controller.cc
index 5034c7e..204314e 100644
--- a/ash/glanceables/glanceables_v2_controller.cc
+++ b/ash/glanceables/glanceables_v2_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/glanceables/glanceables_v2_controller.h"
 
+#include "ash/glanceables/classroom/glanceables_classroom_client.h"
 #include "ash/glanceables/tasks/glanceables_tasks_client.h"
 #include "ash/public/cpp/session/session_controller.h"
 #include "base/check.h"
@@ -32,6 +33,13 @@
   clients_registry_.insert_or_assign(account_id, registration);
 }
 
+GlanceablesClassroomClient* GlanceablesV2Controller::GetClassroomClient()
+    const {
+  const auto iter = clients_registry_.find(active_account_id_);
+  return iter != clients_registry_.end() ? iter->second.classroom_client.get()
+                                         : nullptr;
+}
+
 GlanceablesTasksClient* GlanceablesV2Controller::GetTasksClient() const {
   const auto iter = clients_registry_.find(active_account_id_);
   return iter != clients_registry_.end() ? iter->second.tasks_client.get()
diff --git a/ash/glanceables/glanceables_v2_controller.h b/ash/glanceables/glanceables_v2_controller.h
index 8ee7722..a86dd48e 100644
--- a/ash/glanceables/glanceables_v2_controller.h
+++ b/ash/glanceables/glanceables_v2_controller.h
@@ -13,6 +13,7 @@
 
 namespace ash {
 
+class GlanceablesClassroomClient;
 class GlanceablesTasksClient;
 
 // Root glanceables controller.
@@ -22,6 +23,8 @@
  public:
   // Convenience wrapper to pass all clients from browser to ash at once.
   struct ClientsRegistration {
+    raw_ptr<GlanceablesClassroomClient, ExperimentalAsh> classroom_client =
+        nullptr;
     raw_ptr<GlanceablesTasksClient, ExperimentalAsh> tasks_client = nullptr;
   };
 
@@ -37,6 +40,10 @@
   void UpdateClientsRegistration(const AccountId& account_id,
                                  const ClientsRegistration& registration);
 
+  // Returns a classroom client pointer associated with the
+  // `active_account_id_`. Could return `nullptr`.
+  GlanceablesClassroomClient* GetClassroomClient() const;
+
   // Returns a tasks client pointer associated with the `active_account_id_`.
   // Could return `nullptr`.
   GlanceablesTasksClient* GetTasksClient() const;
diff --git a/ash/keyboard/ui/keyboard_ukm_recorder_unittest.cc b/ash/keyboard/ui/keyboard_ukm_recorder_unittest.cc
index 1ba664d..21ab347 100644
--- a/ash/keyboard/ui/keyboard_ukm_recorder_unittest.cc
+++ b/ash/keyboard/ui/keyboard_ukm_recorder_unittest.cc
@@ -15,7 +15,7 @@
   base::test::TaskEnvironment env;
 
   ukm::TestAutoSetUkmRecorder test_recorder;
-  test_recorder.UpdateRecording(ukm::UkmConsentState(ukm::MSBB));
+  test_recorder.UpdateRecording({ukm::MSBB});
   EXPECT_EQ(0u, test_recorder.entries_count());
 
   RecordUkmKeyboardShown(ukm::SourceId(), ui::TEXT_INPUT_TYPE_NONE);
@@ -28,7 +28,7 @@
   base::test::TaskEnvironment env;
 
   ukm::TestAutoSetUkmRecorder test_recorder;
-  test_recorder.UpdateRecording(ukm::MSBB);
+  test_recorder.UpdateRecording({ukm::MSBB});
   ASSERT_EQ(0u, test_recorder.entries_count());
 
   const ukm::SourceId source =
diff --git a/ash/metrics/feature_discovery_duration_reporter_impl.h b/ash/metrics/feature_discovery_duration_reporter_impl.h
index a199a03c..dd24d54 100644
--- a/ash/metrics/feature_discovery_duration_reporter_impl.h
+++ b/ash/metrics/feature_discovery_duration_reporter_impl.h
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/feature_discovery_duration_reporter.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/session/session_controller_impl.h"
+#include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 
 class PrefRegistrySimple;
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index f3d6005..f67d6a6a 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -420,7 +420,7 @@
     "//chromeos/ui/clipboard_history",
     "//chromeos/ui/frame",
     "//chromeos/ui/vector_icons",
-    "//components/access_code_cast/common",
+    "//components/access_code_cast/common:metrics",
     "//components/language/core/browser:browser",
     "//components/pref_registry",
     "//components/prefs",
@@ -570,7 +570,7 @@
   deps = [
     ":cpp",
     "//base",
-    "//components/access_code_cast/common",
+    "//components/access_code_cast/common:metrics",
     "//mojo/public/cpp/bindings",
     "//services/data_decoder/public/cpp:service_provider",
     "//services/data_decoder/public/mojom",
diff --git a/ash/public/cpp/ambient/ambient_client.h b/ash/public/cpp/ambient/ambient_client.h
index b4eacc26..16c46c16 100644
--- a/ash/public/cpp/ambient/ambient_client.h
+++ b/ash/public/cpp/ambient/ambient_client.h
@@ -55,6 +55,10 @@
   virtual scoped_refptr<network::SharedURLLoaderFactory>
   GetURLLoaderFactory() = 0;
 
+  // Return the URL loader factory associated with the sign in profile.
+  virtual scoped_refptr<network::SharedURLLoaderFactory>
+  GetSigninURLLoaderFactory() = 0;
+
   // Requests a connection to the device service's |WakeLockProvider|
   // from the browser.
   virtual void RequestWakeLockProvider(
diff --git a/ash/public/cpp/shelf_item.cc b/ash/public/cpp/shelf_item.cc
index 6448923..723cc08 100644
--- a/ash/public/cpp/shelf_item.cc
+++ b/ash/public/cpp/shelf_item.cc
@@ -10,4 +10,8 @@
 ShelfItem::ShelfItem(const ShelfItem& shelf_item) = default;
 ShelfItem::~ShelfItem() = default;
 
+bool ShelfItem::IsPinStateForced() const {
+  return pinned_by_policy || pin_state_forced_by_type;
+}
+
 }  // namespace ash
diff --git a/ash/public/cpp/shelf_item.h b/ash/public/cpp/shelf_item.h
index 1189eca..96811f7 100644
--- a/ash/public/cpp/shelf_item.h
+++ b/ash/public/cpp/shelf_item.h
@@ -20,6 +20,9 @@
   ShelfItem(const ShelfItem& shelf_item);
   ~ShelfItem();
 
+  // Returns true if the pin state of the item is forced and can not be changed.
+  bool IsPinStateForced() const;
+
   ShelfItemType type = TYPE_UNDEFINED;
 
   // Image to display in the shelf.
@@ -51,6 +54,10 @@
   // not be modifiable by user.
   bool pinned_by_policy = false;
 
+  // Whether the item pin state is forced according to its app type. The pin
+  // state can not be modified by user if this is set to true.
+  bool pin_state_forced_by_type = false;
+
   // Whether the item has a notification.
   bool has_notification = false;
 };
diff --git a/ash/public/cpp/shelf_model.cc b/ash/public/cpp/shelf_model.cc
index d84354c..6e6c4f5 100644
--- a/ash/public/cpp/shelf_model.cc
+++ b/ash/public/cpp/shelf_model.cc
@@ -79,7 +79,7 @@
 
   ShelfItem item = items_[index];
   DCHECK_EQ(item.type, TYPE_APP);
-  DCHECK(!item.pinned_by_policy);
+  DCHECK(!item.IsPinStateForced());
   item.type = TYPE_PINNED_APP;
   Set(index, item);
 }
diff --git a/ash/public/mojom/accelerator_configuration.mojom b/ash/public/mojom/accelerator_configuration.mojom
index b1bb2309a..dcf58f5 100644
--- a/ash/public/mojom/accelerator_configuration.mojom
+++ b/ash/public/mojom/accelerator_configuration.mojom
@@ -30,4 +30,6 @@
   kMissingModifier = 7,
   // Error - shift cannot be the only modifier.
   kShiftOnlyNotAllowed = 8,
+  // Error - reached maximum number of allowed accelerators for the action.
+  kMaximumAcceleratorsReached = 9,
 };
\ No newline at end of file
diff --git a/ash/quick_pair/repository/fast_pair/device_address_map.h b/ash/quick_pair/repository/fast_pair/device_address_map.h
index 5ceb261..7e43bd7 100644
--- a/ash/quick_pair/repository/fast_pair/device_address_map.h
+++ b/ash/quick_pair/repository/fast_pair/device_address_map.h
@@ -9,6 +9,7 @@
 
 #include "ash/quick_pair/common/device.h"
 #include "base/containers/flat_map.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/ash/quick_pair/repository/fast_pair/device_image_store.h b/ash/quick_pair/repository/fast_pair/device_image_store.h
index ad1d312..fce56cf 100644
--- a/ash/quick_pair/repository/fast_pair/device_image_store.h
+++ b/ash/quick_pair/repository/fast_pair/device_image_store.h
@@ -10,6 +10,7 @@
 
 #include "ash/quick_pair/repository/fast_pair/device_metadata.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/values.h"
 #include "chromeos/ash/services/bluetooth_config/public/cpp/device_image_info.h"
diff --git a/ash/shelf/shelf_test_util.cc b/ash/shelf/shelf_test_util.cc
index 5199127b..5fe8ca29 100644
--- a/ash/shelf/shelf_test_util.cc
+++ b/ash/shelf/shelf_test_util.cc
@@ -40,6 +40,25 @@
   return item;
 }
 
+// static
+ShelfItem ShelfTestUtil::AddAppNotPinnable(const std::string& id) {
+  ShelfController* controller = Shell::Get()->shelf_controller();
+  ShelfItem item;
+  item.type = TYPE_APP;
+  item.status = STATUS_RUNNING;
+  item.id = ShelfID(id);
+  item.pin_state_forced_by_type = true;
+
+  // All focusable objects are expected to have an accessible name to pass
+  // the accessibility paint checks. ShelfAppButton will use the item's
+  // title as the accessible name. Since this item is purely for testing,
+  // use its id as the title in order for the unit tests to pass the checks.
+  item.title = base::UTF8ToUTF16(id);
+  controller->model()->Add(item,
+                           std::make_unique<TestShelfItemDelegate>(item.id));
+  return item;
+}
+
 void WaitForOverviewAnimation(bool enter) {
   ShellTestApi().WaitForOverviewAnimationState(
       enter ? OverviewAnimationState::kEnterAnimationComplete
diff --git a/ash/shelf/shelf_test_util.h b/ash/shelf/shelf_test_util.h
index 35a2529..7d6a135 100644
--- a/ash/shelf/shelf_test_util.h
+++ b/ash/shelf/shelf_test_util.h
@@ -29,6 +29,9 @@
   static ShelfItem AddAppShortcutWithIcon(const std::string& id,
                                           ShelfItemType type,
                                           gfx::ImageSkia icon);
+
+  // Adds an app that is not pinnable to the shelf model.
+  static ShelfItem AddAppNotPinnable(const std::string& id);
 };
 
 // Waits for an overview enter animation if |enter|; waits for an overview exit
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index f612bd7..28ecca8d 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -2000,7 +2000,7 @@
 
   // Note that |drag_and_drop_shelf_id_| is set only when the current drag view
   // is from app list, which can not be dragged to the unpinned app side.
-  return !ShelfItemForView(drag_view)->pinned_by_policy &&
+  return !ShelfItemForView(drag_view)->IsPinStateForced() &&
          drag_and_drop_shelf_id_ == ShelfID() && can_change_pin_state;
 }
 
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index 66ff26c..df7f857 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -31,6 +31,7 @@
 #include "ash/shelf/home_button.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_app_button.h"
+#include "ash/shelf/shelf_controller.h"
 #include "ash/shelf/shelf_focus_cycler.h"
 #include "ash/shelf/shelf_navigation_widget.h"
 #include "ash/shelf/shelf_observer.h"
@@ -868,6 +869,36 @@
   EXPECT_EQ(test_api_->GetSeparatorIndex(), absl::nullopt);
 }
 
+// Ensure that the unpinnable apps can not be pinned by dragging.
+TEST_F(ShelfViewTest, NotPinnableItemCantBePinnedByDragging) {
+  std::vector<std::pair<ShelfID, views::View*>> id_map;
+  SetupForDragTest(&id_map);
+  size_t pinned_apps_size = id_map.size();
+
+  // Add an unpinnable app.
+  const ShelfItem unpinnable_app =
+      ShelfTestUtil::AddAppNotPinnable(base::NumberToString(id_++));
+  const ShelfID id = unpinnable_app.id;
+  id_map.emplace_back(id, GetButtonByID(id));
+
+  ASSERT_TRUE(GetButtonByID(id)->state() & ShelfAppButton::STATE_RUNNING);
+  ASSERT_FALSE(IsAppPinned(id));
+  EXPECT_EQ(test_api_->GetSeparatorIndex(), pinned_apps_size - 1);
+
+  // Drag an unpinnable app and move it to the beginning of the shelf. The app
+  // can not be moved across the separator so the dragged app will stay unpinned
+  // beside the separator after release.
+  views::View* dragged_button =
+      SimulateDrag(ShelfView::MOUSE, id_map.size() - 1, 0, false);
+  EXPECT_EQ(1, GetHapticTickEventsCount());
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, false);
+  test_api_->RunMessageLoopUntilAnimationsDone();
+  EXPECT_EQ(1, GetHapticTickEventsCount());
+  EXPECT_EQ(test_api_->GetSeparatorIndex(), pinned_apps_size - 1);
+  EXPECT_FALSE(IsAppPinned(id));
+}
+
 // Check that separator index updates as expected when a drag view is dragged
 // over it.
 TEST_F(ShelfViewTest, DragAppAroundSeparator) {
diff --git a/ash/shell.cc b/ash/shell.cc
index af714cb4..8de795e 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -687,6 +687,7 @@
     DCHECK(rwc->GetHost()->dispatcher()->in_shutdown());
   }
 #endif
+  booting_animation_controller_.reset();
   login_unlock_throughput_recorder_.reset();
 
   hud_display::HUDDisplayView::Destroy();
diff --git a/ash/system/accessibility/autoclick_scroll_bubble_controller.cc b/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
index 1233b65..c99ef40 100644
--- a/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
@@ -8,6 +8,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/unified/unified_system_tray_view.h"
@@ -16,6 +17,7 @@
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
 #include "ui/aura/window_tree_host.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/events/event_utils.h"
@@ -45,6 +47,10 @@
 AutoclickScrollBubbleController::~AutoclickScrollBubbleController() {
   if (bubble_widget_ && !bubble_widget_->IsClosed())
     bubble_widget_->CloseNow();
+
+  bubble_view_ = nullptr;
+  scroll_view_ = nullptr;
+  bubble_widget_ = nullptr;
 }
 
 void AutoclickScrollBubbleController::UpdateAnchorRect(
@@ -270,4 +276,8 @@
   scroll_view_ = nullptr;
 }
 
+std::u16string AutoclickScrollBubbleController::GetAccessibleNameForBubble() {
+  return l10n_util::GetStringUTF16(IDS_ASH_AUTOCLICK_SCROLL_BUBBLE);
+}
+
 }  // namespace ash
diff --git a/ash/system/accessibility/autoclick_scroll_bubble_controller.h b/ash/system/accessibility/autoclick_scroll_bubble_controller.h
index f246cd7..0c04097 100644
--- a/ash/system/accessibility/autoclick_scroll_bubble_controller.h
+++ b/ash/system/accessibility/autoclick_scroll_bubble_controller.h
@@ -44,6 +44,7 @@
 
   // TrayBubbleView::Delegate:
   void BubbleViewDestroyed() override;
+  std::u16string GetAccessibleNameForBubble() override;
 
  private:
   friend class AutoclickMenuBubbleControllerTest;
diff --git a/ash/system/accessibility/autoclick_scroll_view.cc b/ash/system/accessibility/autoclick_scroll_view.cc
index c1674c5..ccc460b8 100644
--- a/ash/system/accessibility/autoclick_scroll_view.cc
+++ b/ash/system/accessibility/autoclick_scroll_view.cc
@@ -359,35 +359,26 @@
 
 // ------ AutoclickScrollView  ------ //
 
-AutoclickScrollView::AutoclickScrollView()
-    : scroll_up_button_(new AutoclickScrollButton(
-          AutoclickController::ScrollPadAction::kScrollUp,
-          kAutoclickScrollUpIcon,
-          IDS_ASH_AUTOCLICK_SCROLL_UP,
-          ButtonId::kScrollUp)),
-      scroll_down_button_(new AutoclickScrollButton(
-          AutoclickController::ScrollPadAction::kScrollDown,
-          kAutoclickScrollDownIcon,
-          IDS_ASH_AUTOCLICK_SCROLL_DOWN,
-          ButtonId::kScrollDown)),
-      scroll_left_button_(new AutoclickScrollButton(
-          AutoclickController::ScrollPadAction::kScrollLeft,
-          kAutoclickScrollLeftIcon,
-          IDS_ASH_AUTOCLICK_SCROLL_LEFT,
-          ButtonId::kScrollLeft)),
-      scroll_right_button_(new AutoclickScrollButton(
-          AutoclickController::ScrollPadAction::kScrollRight,
-          kAutoclickScrollRightIcon,
-          IDS_ASH_AUTOCLICK_SCROLL_RIGHT,
-          ButtonId::kScrollRight)),
-      close_scroll_button_(new AutoclickScrollCloseButton()) {
+AutoclickScrollView::AutoclickScrollView() {
   SetPreferredSize(gfx::Size(kScrollPadButtonHypotenuseDips,
                              kScrollPadButtonHypotenuseDips));
-  AddChildView(close_scroll_button_.get());
-  AddChildView(scroll_up_button_.get());
-  AddChildView(scroll_down_button_.get());
-  AddChildView(scroll_left_button_.get());
-  AddChildView(scroll_right_button_.get());
+  close_scroll_button_ =
+      AddChildView(std::make_unique<AutoclickScrollCloseButton>());
+  scroll_up_button_ = AddChildView(std::make_unique<AutoclickScrollButton>(
+      AutoclickController::ScrollPadAction::kScrollUp, kAutoclickScrollUpIcon,
+      IDS_ASH_AUTOCLICK_SCROLL_UP, ButtonId::kScrollUp));
+  scroll_down_button_ = AddChildView(std::make_unique<AutoclickScrollButton>(
+      AutoclickController::ScrollPadAction::kScrollDown,
+      kAutoclickScrollDownIcon, IDS_ASH_AUTOCLICK_SCROLL_DOWN,
+      ButtonId::kScrollDown));
+  scroll_left_button_ = AddChildView(std::make_unique<AutoclickScrollButton>(
+      AutoclickController::ScrollPadAction::kScrollLeft,
+      kAutoclickScrollLeftIcon, IDS_ASH_AUTOCLICK_SCROLL_LEFT,
+      ButtonId::kScrollLeft));
+  scroll_right_button_ = AddChildView(std::make_unique<AutoclickScrollButton>(
+      AutoclickController::ScrollPadAction::kScrollRight,
+      kAutoclickScrollRightIcon, IDS_ASH_AUTOCLICK_SCROLL_RIGHT,
+      ButtonId::kScrollRight));
 }
 
 void AutoclickScrollView::Layout() {
diff --git a/ash/system/accessibility/autoclick_scroll_view.h b/ash/system/accessibility/autoclick_scroll_view.h
index 5bbacad..f900c9e 100644
--- a/ash/system/accessibility/autoclick_scroll_view.h
+++ b/ash/system/accessibility/autoclick_scroll_view.h
@@ -77,12 +77,11 @@
   void Layout() override;
 
   // Unowned. Owned by views hierarchy.
-  const raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_up_button_;
-  const raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_down_button_;
-  const raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_left_button_;
-  const raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_right_button_;
-  const raw_ptr<AutoclickScrollCloseButton, ExperimentalAsh>
-      close_scroll_button_;
+  raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_up_button_;
+  raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_down_button_;
+  raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_left_button_;
+  raw_ptr<AutoclickScrollButton, ExperimentalAsh> scroll_right_button_;
+  raw_ptr<AutoclickScrollCloseButton, ExperimentalAsh> close_scroll_button_;
 };
 
 }  // namespace ash
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl_pixeltest.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl_pixeltest.cc
new file mode 100644
index 0000000..d22f2715
--- /dev/null
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl_pixeltest.cc
@@ -0,0 +1,99 @@
+// 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 <string>
+
+#include "ash/constants/ash_features.h"
+#include "ash/system/unified/quick_settings_view.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/system/unified/unified_system_tray_bubble.h"
+#include "ash/system/unified/unified_system_tray_controller.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_helper.h"
+#include "ash/test/pixel/ash_pixel_differ.h"
+#include "ash/test/pixel/ash_pixel_test_init_params.h"
+#include "base/test/scoped_feature_list.h"
+#include "chromeos/ash/services/bluetooth_config/fake_device_cache.h"
+#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
+#include "chromeos/ash/services/bluetooth_config/scoped_bluetooth_config_test_helper.h"
+#include "chromeos/constants/chromeos_features.h"
+
+namespace ash {
+namespace {
+
+using bluetooth_config::ScopedBluetoothConfigTestHelper;
+using bluetooth_config::mojom::BluetoothDeviceProperties;
+using bluetooth_config::mojom::DeviceConnectionState;
+using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
+using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;
+
+// Creates a paired Bluetooth device.
+PairedBluetoothDevicePropertiesPtr CreatePairedDevice(
+    DeviceConnectionState connection_state,
+    const std::u16string& public_name) {
+  PairedBluetoothDevicePropertiesPtr paired_properties =
+      PairedBluetoothDeviceProperties::New();
+  paired_properties->device_properties = BluetoothDeviceProperties::New();
+  paired_properties->device_properties->connection_state = connection_state;
+  paired_properties->device_properties->public_name = public_name;
+  return paired_properties;
+}
+
+// Pixel tests for the quick settings Bluetooth detailed view.
+class BluetoothDetailedViewImplPixelTest : public AshTestBase {
+ public:
+  BluetoothDetailedViewImplPixelTest() {
+    feature_list_.InitWithFeatures(
+        {features::kQsRevamp, chromeos::features::kJelly}, {});
+  }
+
+  // AshTestBase:
+  absl::optional<pixel_test::InitParams> CreatePixelTestInitParams()
+      const override {
+    return pixel_test::InitParams();
+  }
+
+  // Sets the list of paired devices in the device cache.
+  void SetPairedDevices(
+      std::vector<PairedBluetoothDevicePropertiesPtr> paired_devices) {
+    ash_test_helper()
+        ->bluetooth_config_test_helper()
+        ->fake_device_cache()
+        ->SetPairedDevices(std::move(paired_devices));
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(BluetoothDetailedViewImplPixelTest, Basics) {
+  // Create test devices.
+  std::vector<PairedBluetoothDevicePropertiesPtr> paired_devices;
+  paired_devices.push_back(
+      CreatePairedDevice(DeviceConnectionState::kConnected, u"Keyboard"));
+  paired_devices.push_back(
+      CreatePairedDevice(DeviceConnectionState::kNotConnected, u"Mouse"));
+  SetPairedDevices(std::move(paired_devices));
+
+  // Show the system tray bubble.
+  UnifiedSystemTray* system_tray = GetPrimaryUnifiedSystemTray();
+  system_tray->ShowBubble();
+  ASSERT_TRUE(system_tray->bubble());
+
+  // Show the Bluetooth detailed view.
+  system_tray->bubble()
+      ->unified_system_tray_controller()
+      ->ShowBluetoothDetailedView();
+  views::View* detailed_view =
+      system_tray->bubble()->quick_settings_view()->detailed_view();
+  ASSERT_TRUE(detailed_view);
+
+  // Compare pixels.
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "check_view",
+      /*revision_number=*/0, detailed_view));
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/system/diagnostics/networking_log.h b/ash/system/diagnostics/networking_log.h
index 12703114..cfecd76 100644
--- a/ash/system/diagnostics/networking_log.h
+++ b/ash/system/diagnostics/networking_log.h
@@ -11,6 +11,7 @@
 #include "ash/ash_export.h"
 #include "ash/system/diagnostics/async_log.h"
 #include "ash/webui/diagnostics_ui/mojom/network_health_provider.mojom.h"
+#include "base/gtest_prod_util.h"
 
 namespace ash {
 namespace diagnostics {
diff --git a/ash/system/message_center/ash_notification_input_container.h b/ash/system/message_center/ash_notification_input_container.h
index db6ae43..34b1f86 100644
--- a/ash/system/message_center/ash_notification_input_container.h
+++ b/ash/system/message_center/ash_notification_input_container.h
@@ -6,6 +6,7 @@
 #define ASH_SYSTEM_MESSAGE_CENTER_ASH_NOTIFICATION_INPUT_CONTAINER_H_
 
 #include "ash/ash_export.h"
+#include "base/gtest_prod_util.h"
 #include "ui/message_center/views/notification_input_container.h"
 
 namespace ash {
diff --git a/ash/system/message_center/metrics_utils_unittest.cc b/ash/system/message_center/metrics_utils_unittest.cc
index 57a9903..1c66f97 100644
--- a/ash/system/message_center/metrics_utils_unittest.cc
+++ b/ash/system/message_center/metrics_utils_unittest.cc
@@ -4,8 +4,12 @@
 
 #include "ash/system/message_center/metrics_utils.h"
 
+#include <memory>
+
+#include "ash/constants/ash_features.h"
 #include "ash/system/message_center/ash_message_popup_collection.h"
 #include "ash/system/message_center/unified_message_center_bubble.h"
+#include "ash/system/notification_center/notification_center_test_api.h"
 #include "ash/system/notification_center/notification_center_view.h"
 #include "ash/system/notification_center/notification_list_view.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -135,6 +139,10 @@
     AshTestBase::SetUp();
     test_delegate_ =
         base::MakeRefCounted<message_center::NotificationDelegate>();
+    notification_center_test_api_ = std::make_unique<NotificationCenterTestApi>(
+        /*tray=*/features::IsQsRevampEnabled()
+            ? GetPrimaryNotificationCenterTray()
+            : nullptr);
   }
 
   // Create a test notification. Noted that the notifications are using the same
@@ -178,18 +186,12 @@
 
   // Get the notification view from message center associated with `id`.
   views::View* GetNotificationViewFromMessageCenter(const std::string& id) {
-    return GetPrimaryUnifiedSystemTray()
-        ->message_center_bubble()
-        ->notification_center_view()
-        ->notification_list_view()
-        ->GetMessageViewForNotificationId(id);
+    return notification_center_test_api_->GetNotificationViewForId(id);
   }
 
   // Get the popup notification view associated with `id`.
   views::View* GetPopupNotificationView(const std::string& id) {
-    return GetPrimaryUnifiedSystemTray()
-        ->GetMessagePopupCollection()
-        ->GetMessageViewForNotificationId(id);
+    return notification_center_test_api_->GetPopupViewForId(id);
   }
 
   void ClickView(views::View* view) {
@@ -209,8 +211,13 @@
     return test_delegate_;
   }
 
+  NotificationCenterTestApi* notification_center_test_api() {
+    return notification_center_test_api_.get();
+  }
+
  private:
   scoped_refptr<message_center::NotificationDelegate> test_delegate_;
+  std::unique_ptr<NotificationCenterTestApi> notification_center_test_api_;
 
   // Used to create test notification. This represents the current available
   // number that we can use to create the next test notification. This id will
@@ -225,7 +232,7 @@
   // Add the notification and get its view in message center.
   message_center::MessageCenter::Get()->AddNotification(
       std::make_unique<message_center::Notification>(*notification));
-  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  notification_center_test_api()->ToggleBubble();
 
   // A click to a notification without a delegate should record a bad click.
   ClickView(GetNotificationViewFromMessageCenter(notification->id()));
@@ -243,7 +250,7 @@
   // Add the notification and get its view in message center.
   message_center::MessageCenter::Get()->AddNotification(
       std::make_unique<message_center::Notification>(*notification));
-  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  notification_center_test_api()->ToggleBubble();
 
   // A click to a notification with a delegate should record a good click.
   ClickView(GetNotificationViewFromMessageCenter(notification->id()));
@@ -254,6 +261,10 @@
 }
 
 TEST_F(MessageCenterMetricsUtilsTest, RecordHover) {
+  // TODO(b/281705636): Fix metrics recording when QS revamp is enabled.
+  if (features::IsQsRevampEnabled()) {
+    return;
+  }
   base::HistogramTester histograms;
   auto notification = CreateTestNotification();
 
@@ -261,13 +272,14 @@
   message_center::MessageCenter::Get()->AddNotification(
       std::make_unique<message_center::Notification>(*notification));
 
-  auto* popup = GetPopupNotificationView(notification->id());
+  auto* popup =
+      notification_center_test_api()->GetPopupViewForId(notification->id());
   // Move the mouse hover on the popup notification view, expect hover action
   // recorded.
   HoverOnView(popup);
   histograms.ExpectTotalCount("Notifications.Cros.Actions.Popup.Hover", 1);
 
-  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  notification_center_test_api()->ToggleBubble();
   auto* notification_view =
       GetNotificationViewFromMessageCenter(notification->id());
 
diff --git a/ash/system/phonehub/app_stream_launcher_view.h b/ash/system/phonehub/app_stream_launcher_view.h
index ed6e7ed1..3595931 100644
--- a/ash/system/phonehub/app_stream_launcher_view.h
+++ b/ash/system/phonehub/app_stream_launcher_view.h
@@ -10,6 +10,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/system/phonehub/phone_hub_content_view.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "chromeos/ash/components/phonehub/notification.h"
diff --git a/ash/system/phonehub/camera_roll_thumbnail.h b/ash/system/phonehub/camera_roll_thumbnail.h
index 80fe78e5..1a5e4bd8 100644
--- a/ash/system/phonehub/camera_roll_thumbnail.h
+++ b/ash/system/phonehub/camera_roll_thumbnail.h
@@ -8,6 +8,7 @@
 #include "ash/ash_export.h"
 #include "ash/system/phonehub/camera_roll_menu_model.h"
 #include "ash/system/phonehub/phone_hub_metrics.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ash/components/phonehub/camera_roll_item.h"
 #include "ui/base/metadata/metadata_header_macros.h"
diff --git a/ash/system/phonehub/phone_hub_tray.h b/ash/system/phonehub/phone_hub_tray.h
index 3583fb0..0895f693 100644
--- a/ash/system/phonehub/phone_hub_tray.h
+++ b/ash/system/phonehub/phone_hub_tray.h
@@ -19,6 +19,7 @@
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "chromeos/ash/components/phonehub/app_stream_manager.h"
diff --git a/ash/system/privacy_hub/privacy_hub_controller.cc b/ash/system/privacy_hub/privacy_hub_controller.cc
index 47404ac..3d94ef5b 100644
--- a/ash/system/privacy_hub/privacy_hub_controller.cc
+++ b/ash/system/privacy_hub/privacy_hub_controller.cc
@@ -25,6 +25,7 @@
 void PrivacyHubController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
   registry->RegisterBooleanPref(prefs::kUserCameraAllowed, true);
   registry->RegisterBooleanPref(prefs::kUserMicrophoneAllowed, true);
+  registry->RegisterBooleanPref(prefs::kUserSpeakOnMuteDetectionEnabled, false);
   registry->RegisterBooleanPref(prefs::kUserGeolocationAllowed, true);
 }
 
diff --git a/ash/system/unified/feature_tile.cc b/ash/system/unified/feature_tile.cc
index afb2bfc..2948dcc 100644
--- a/ash/system/unified/feature_tile.cc
+++ b/ash/system/unified/feature_tile.cc
@@ -38,7 +38,7 @@
 // Tile constants
 constexpr int kIconSize = 20;
 constexpr int kButtonRadius = 16;
-constexpr int kFocusRingPadding = 2;
+constexpr float kFocusRingPadding = 3.0f;
 
 // Primary tile constants
 constexpr int kPrimarySubtitleLineHeight = 18;
@@ -79,8 +79,22 @@
                          bool is_togglable,
                          TileType type)
     : Button(callback), is_togglable_(is_togglable), type_(type) {
-  views::InstallRoundRectHighlightPathGenerator(
-      this, gfx::Insets(-kFocusRingPadding), kButtonRadius + kFocusRingPadding);
+  // Set up ink drop on click. The corner radius must match the button
+  // background corner radius, see UpdateColors().
+  // TODO(jamescook): Consider adding support for highlight-path-based
+  // backgrounds so we don't have to match the shape manually. For example, add
+  // something like CreateThemedHighlightPathBackground() to
+  // ui/views/background.h.
+  views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                kButtonRadius);
+  auto* ink_drop = views::InkDrop::Get(this);
+  ink_drop->SetMode(InkDropHost::InkDropMode::ON);
+  ink_drop->GetInkDrop()->SetShowHighlightOnHover(false);
+  ink_drop->SetVisibleOpacity(1.0f);  // The colors already contain opacity.
+
+  // The focus ring appears slightly outside the tile bounds.
+  views::FocusRing::Get(this)->SetHaloInset(-kFocusRingPadding);
+
   CreateChildViews();
   UpdateColors();
 
@@ -94,7 +108,11 @@
       base::Unretained(this)));
 }
 
-FeatureTile::~FeatureTile() = default;
+FeatureTile::~FeatureTile() {
+  // Remove the InkDrop explicitly so FeatureTile::RemoveLayerFromRegions() is
+  // called before views::View teardown.
+  views::InkDrop::Remove(this);
+}
 
 void FeatureTile::CreateChildViews() {
   const bool is_compact = type_ == TileType::kCompact;
@@ -104,6 +122,10 @@
                                      ? views::LayoutOrientation::kVertical
                                      : views::LayoutOrientation::kHorizontal);
 
+  ink_drop_container_ =
+      AddChildView(std::make_unique<views::InkDropContainerView>());
+  layout_manager->SetChildViewIgnoredByLayout(ink_drop_container_, true);
+
   auto* focus_ring = views::FocusRing::Get(this);
   focus_ring->SetColorId(cros_tokens::kCrosSysFocusRing);
   // Since the focus ring doesn't set a LayoutManager it won't get drawn unless
@@ -241,6 +263,10 @@
 
   SetBackground(views::CreateThemedRoundedRectBackground(background_color,
                                                          kButtonRadius));
+  auto* ink_drop = views::InkDrop::Get(this);
+  ink_drop->SetBaseColorId(toggled_
+                               ? cros_tokens::kCrosSysRipplePrimary
+                               : cros_tokens::kCrosSysRippleNeutralOnSubtle);
 
   auto icon_image_model = ui::ImageModel::FromVectorIcon(
       *vector_icon_, foreground_color, kIconSize);
@@ -305,6 +331,19 @@
   sub_label_->SetVisible(visible);
 }
 
+void FeatureTile::AddLayerToRegion(ui::Layer* layer,
+                                   views::LayerRegion region) {
+  // This routes background layers to `ink_drop_container_` instead of `this` to
+  // avoid painting effects underneath our background.
+  ink_drop_container_->AddLayerToRegion(layer, region);
+}
+
+void FeatureTile::RemoveLayerFromRegions(ui::Layer* layer) {
+  // This routes background layers to `ink_drop_container_` instead of `this` to
+  // avoid painting effects underneath our background.
+  ink_drop_container_->RemoveLayerFromRegions(layer);
+}
+
 ui::ColorId FeatureTile::GetIconColorId() const {
   if (!GetEnabled()) {
     return cros_tokens::kCrosSysDisabled;
@@ -327,6 +366,10 @@
                                : cros_tokens::kCrosSysRippleNeutralOnSubtle);
   // The ripple base color includes opacity.
   ink_drop->SetVisibleOpacity(1.0f);
+
+  // Ensure the new color applies even if the hover highlight or ripple is
+  // already showing.
+  ink_drop->GetInkDrop()->HostViewThemeChanged();
 }
 
 void FeatureTile::UpdateIconButtonFocusRingColor() {
diff --git a/ash/system/unified/feature_tile.h b/ash/system/unified/feature_tile.h
index 3991b1f..7e3f9e2 100644
--- a/ash/system/unified/feature_tile.h
+++ b/ash/system/unified/feature_tile.h
@@ -20,6 +20,7 @@
 class FlexLayoutView;
 class ImageButton;
 class ImageView;
+class InkDropContainerView;
 class Label;
 }  // namespace views
 
@@ -126,6 +127,10 @@
   friend class BluetoothFeaturePodControllerTest;
   friend class NotificationCounterViewTest;
 
+  // views::View:
+  void AddLayerToRegion(ui::Layer* layer, views::LayerRegion region) override;
+  void RemoveLayerFromRegions(ui::Layer* layer) override;
+
   // Returns the color id to use for the `icon_button_` and `drill_in_arrow_`
   // based on the tile's enabled and toggled state.
   ui::ColorId GetIconColorId() const;
@@ -139,6 +144,10 @@
   // Updates the color of `drill_in_arrow_` for better visibility.
   void UpdateDrillInArrowColor();
 
+  // Ensures the ink drop is painted above the button's background.
+  raw_ptr<views::InkDropContainerView, ExperimentalAsh> ink_drop_container_ =
+      nullptr;
+
   // The vector icon for the tile, if one is set.
   raw_ptr<const gfx::VectorIcon, ExperimentalAsh> vector_icon_ = nullptr;
 
diff --git a/ash/system/unified/feature_tile_pixeltest.cc b/ash/system/unified/feature_tile_pixeltest.cc
new file mode 100644
index 0000000..a9ebe338
--- /dev/null
+++ b/ash/system/unified/feature_tile_pixeltest.cc
@@ -0,0 +1,123 @@
+// 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 <memory>
+
+#include "ash/system/unified/feature_tile.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/pixel/ash_pixel_differ.h"
+#include "ash/test/pixel/ash_pixel_test_init_params.h"
+#include "base/functional/callback_helpers.h"
+#include "base/test/scoped_feature_list.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/background.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/box_layout_view.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace {
+
+// Pixel tests for the quick settings feature tile view.
+class FeatureTilePixelTest : public AshTestBase {
+ public:
+  FeatureTilePixelTest() {
+    feature_list_.InitAndEnableFeature(chromeos::features::kJelly);
+  }
+
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    widget_ = CreateFramelessTestWidget();
+    // Ensure the widget is large enough for the tile's focus ring (which is
+    // drawn outside the tile's bounds).
+    widget_->SetBounds(gfx::Rect(200, 80));
+    auto* contents =
+        widget_->SetContentsView(std::make_unique<views::BoxLayoutView>());
+    contents->SetMainAxisAlignment(
+        views::BoxLayout::MainAxisAlignment::kCenter);
+    contents->SetCrossAxisAlignment(
+        views::BoxLayout::CrossAxisAlignment::kCenter);
+    // The tile colors have transparency, so set a background color so they
+    // render like in production.
+    contents->SetBackground(views::CreateThemedSolidBackground(
+        cros_tokens::kCrosSysSystemBaseElevated));
+  }
+
+  void TearDown() override {
+    widget_.reset();
+    AshTestBase::TearDown();
+  }
+
+  // AshTestBase:
+  absl::optional<pixel_test::InitParams> CreatePixelTestInitParams()
+      const override {
+    return pixel_test::InitParams();
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<views::Widget> widget_;
+};
+
+TEST_F(FeatureTilePixelTest, PrimaryTile) {
+  auto* tile = widget_->GetContentsView()->AddChildView(
+      std::make_unique<FeatureTile>(base::DoNothing(), /*toggleable=*/true,
+                                    FeatureTile::TileType::kPrimary));
+  // Use the default size from go/cros-quick-settings-spec
+  tile->SetPreferredSize(gfx::Size(180, 64));
+  tile->SetVectorIcon(vector_icons::kDogfoodIcon);
+  tile->SetLabel(u"Label");
+  tile->SetSubLabel(u"Sub-label");
+  // Needed for accessibility paint checks.
+  tile->SetTooltipText(u"Tooltip");
+  tile->CreateDecorativeDrillInArrow();
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "basic",
+      /*revision_number=*/0, widget_.get()));
+
+  widget_->GetFocusManager()->SetFocusedView(tile);
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "focused",
+      /*revision_number=*/0, widget_.get()));
+
+  tile->SetToggled(true);
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "toggled",
+      /*revision_number=*/0, widget_.get()));
+}
+
+TEST_F(FeatureTilePixelTest, CompactTile) {
+  auto* tile = widget_->GetContentsView()->AddChildView(
+      std::make_unique<FeatureTile>(base::DoNothing(), /*toggleable=*/true,
+                                    FeatureTile::TileType::kCompact));
+  // Use the default size from go/cros-quick-settings-spec
+  tile->SetPreferredSize(gfx::Size(86, 64));
+  tile->SetVectorIcon(vector_icons::kDogfoodIcon);
+  tile->SetLabel(u"Multi-line label");
+  // Needed for accessibility paint checks.
+  tile->SetTooltipText(u"Tooltip");
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "basic",
+      /*revision_number=*/0, widget_.get()));
+
+  widget_->GetFocusManager()->SetFocusedView(tile);
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "focused",
+      /*revision_number=*/0, widget_.get()));
+
+  tile->SetToggled(true);
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "toggled",
+      /*revision_number=*/0, widget_.get()));
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/system/unified/feature_tile_unittest.cc b/ash/system/unified/feature_tile_unittest.cc
index a35e18e1..52fb04a 100644
--- a/ash/system/unified/feature_tile_unittest.cc
+++ b/ash/system/unified/feature_tile_unittest.cc
@@ -12,10 +12,12 @@
 #include "ash/system/unified/feature_pod_controller_base.h"
 #include "ash/system/unified/feature_tile.h"
 #include "ash/test/ash_test_base.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/vector_icons/vector_icons.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/test/views_test_utils.h"
@@ -339,4 +341,19 @@
   EXPECT_FALSE(tile->IsToggled());
 }
 
+TEST_F(FeatureTileTest, TogglingTileUpdatesInkDropColor) {
+  auto* tile = widget_->SetContentsView(
+      std::make_unique<FeatureTile>(base::DoNothing()));
+  auto* color_provider = tile->GetColorProvider();
+
+  tile->SetToggled(true);
+  EXPECT_EQ(views::InkDrop::Get(tile)->GetBaseColor(),
+            color_provider->GetColor(cros_tokens::kCrosSysRipplePrimary));
+
+  tile->SetToggled(false);
+  EXPECT_EQ(
+      views::InkDrop::Get(tile)->GetBaseColor(),
+      color_provider->GetColor(cros_tokens::kCrosSysRippleNeutralOnSubtle));
+}
+
 }  // namespace ash
diff --git a/ash/system/unified/screen_capture_tray_item_view.h b/ash/system/unified/screen_capture_tray_item_view.h
index 23319cbc..d36d74a 100644
--- a/ash/system/unified/screen_capture_tray_item_view.h
+++ b/ash/system/unified/screen_capture_tray_item_view.h
@@ -11,6 +11,7 @@
 #include "ash/multi_capture/multi_capture_service_client.h"
 #include "ash/system/tray/tray_item_view.h"
 #include "base/containers/fixed_flat_set.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
diff --git a/ash/system/unified/unified_system_tray_controller_unittest.cc b/ash/system/unified/unified_system_tray_controller_unittest.cc
index 7b6fe547..0a848b5 100644
--- a/ash/system/unified/unified_system_tray_controller_unittest.cc
+++ b/ash/system/unified/unified_system_tray_controller_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "ash/system/unified/unified_system_tray_controller.h"
+#include <memory>
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
@@ -58,6 +59,8 @@
 
   // AshTestBase:
   void SetUp() override {
+    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
+    scoped_feature_list_->InitAndDisableFeature(features::kQsRevamp);
     network_config_helper_ =
         std::make_unique<network_config::CrosNetworkConfigTestHelper>();
     AshTestBase::SetUp();
@@ -143,6 +146,8 @@
   std::unique_ptr<UnifiedSystemTrayView> view_;
 
   int preferred_size_changed_count_ = 0;
+
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
 };
 
 class QsRevampUnifiedSystemTrayControllerTest : public AshTestBase {
diff --git a/ash/system/video_conference/bubble/return_to_app_panel.h b/ash/system/video_conference/bubble/return_to_app_panel.h
index 723b95f..212f240 100644
--- a/ash/system/video_conference/bubble/return_to_app_panel.h
+++ b/ash/system/video_conference/bubble/return_to_app_panel.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "ash/ash_export.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list_types.h"
diff --git a/ash/user_education/user_education_types.h b/ash/user_education/user_education_types.h
index 1a40d86b4..c08e314c0 100644
--- a/ash/user_education/user_education_types.h
+++ b/ash/user_education/user_education_types.h
@@ -38,6 +38,7 @@
   kCaptureModeTourPrototype2,
   kHoldingSpaceTourPrototype1,
   kHoldingSpaceTourPrototype2,
+  kTest,
   kWelcomeTourPrototype1,
   kMaxValue = kWelcomeTourPrototype1,
 };
diff --git a/ash/user_education/user_education_util.cc b/ash/user_education/user_education_util.cc
index 37a0a41a..7e0333d 100644
--- a/ash/user_education/user_education_util.cc
+++ b/ash/user_education/user_education_util.cc
@@ -116,6 +116,8 @@
       return "AshHoldingSpaceTourPrototype1";
     case TutorialId::kHoldingSpaceTourPrototype2:
       return "AshHoldingSpaceTourPrototype2";
+    case TutorialId::kTest:
+      return "AshTest";
     case TutorialId::kWelcomeTourPrototype1:
       return "AshWelcomeTourPrototype1";
   }
diff --git a/ash/user_education/views/help_bubble_view_ash.h b/ash/user_education/views/help_bubble_view_ash.h
index 1930c9ce..078a54a 100644
--- a/ash/user_education/views/help_bubble_view_ash.h
+++ b/ash/user_education/views/help_bubble_view_ash.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
diff --git a/ash/webui/camera_app_ui/resources/css/main.css b/ash/webui/camera_app_ui/resources/css/main.css
index 016aab2..09cb99c 100644
--- a/ash/webui/camera_app_ui/resources/css/main.css
+++ b/ash/webui/camera_app_ui/resources/css/main.css
@@ -388,7 +388,7 @@
   --secondary-color: white;
 }
 
-:is(#start-takephoto, #video-snapshot):enabled:hover::after {
+:is(#start-takephoto, #video-snapshot, #stop-takephoto):enabled:hover::after {
   background: var(--cros-sys-hover_on_prominent);
   border-radius: 50%;
   content: '';
@@ -397,7 +397,8 @@
   position: absolute;
 }
 
-:is(#start-takephoto, #video-snapshot):enabled:active svg-wrapper {
+:is(#start-takephoto, #video-snapshot, #stop-takephoto):enabled:active
+  svg-wrapper {
   color: var(--cros-sys-secondary);
 }
 
@@ -405,12 +406,9 @@
   transform: unset;
 }
 
-#stop-takephoto {
-  background-image: url(/images/camera_shutter_photo_stop.svg);
-}
-
-#stop-takephoto:hover {
-  background-image: url(/images/camera_shutter_photo_stop_hover.svg);
+#stop-takephoto svg-wrapper {
+  color: var(--cros-sys-inverse_surface);
+  --secondary-color: var(--cros-sys-secondary_container);
 }
 
 #recordvideo {
diff --git a/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop.svg b/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop.svg
index cbda368f..3e6da3a 100644
--- a/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop.svg
+++ b/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop.svg
@@ -1,16 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
-    <title>btn_stop_timer_1x</title>
-    <desc>Created with Sketch.</desc>
-    <defs>
-        <polygon id="path-1" points="44 29.6114286 42.3885714 28 36 34.3885714 29.6114286 28 28 29.6114286 34.3885714 36 28 42.3885714 29.6114286 44 36 37.6114286 42.3885714 44 44 42.3885714 37.6114286 36"></polygon>
-    </defs>
-    <g id="btn_stop_timer_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <circle id="Oval-3" fill="#BDC1C6" opacity="0.9" cx="36" cy="36" r="30"></circle>
-        <mask id="mask-2" fill="white">
-            <use xlink:href="#path-1"></use>
-        </mask>
-        <use id="ic_close_24px" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
-    </g>
-</svg>
\ No newline at end of file
+<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M60 30C60 46.5686 46.5686 60 30 60C13.4314 60 0 46.5686 0 30C0 13.4314 13.4314 0 30 0C46.5686 0 60 13.4314 60 30Z"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M38 23.88L36.12 22L30 28.12L23.88 22L22 23.88L28.12 30L22 36.12L23.88 38L30 31.88L36.12 38L38 36.12L31.88 30L38 23.88Z" fill="var(--secondary-color)"/>
+</svg>
diff --git a/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop_hover.svg b/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop_hover.svg
deleted file mode 100644
index 1bec4ad..0000000
--- a/ash/webui/camera_app_ui/resources/images/camera_shutter_photo_stop_hover.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
-    <title>btn_stop_timer_hover_1x</title>
-    <desc>Created with Sketch.</desc>
-    <defs>
-        <polygon id="path-1" points="44 29.6114286 42.3885714 28 36 34.3885714 29.6114286 28 28 29.6114286 34.3885714 36 28 42.3885714 29.6114286 44 36 37.6114286 42.3885714 44 44 42.3885714 37.6114286 36"></polygon>
-    </defs>
-    <g id="btn_stop_timer_hover_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <circle id="Oval-3" fill="#BDC1C6" opacity="0.9" cx="36" cy="36" r="30"></circle>
-        <circle id="Oval-3" fill-opacity="0.1" fill="#5F6368" opacity="0.9" cx="36" cy="36" r="30"></circle>
-        <mask id="mask-2" fill="white">
-            <use xlink:href="#path-1"></use>
-        </mask>
-        <use id="ic_close_24px" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
-    </g>
-</svg>
\ No newline at end of file
diff --git a/ash/webui/camera_app_ui/resources/images/images.gni b/ash/webui/camera_app_ui/resources/images/images.gni
index 50a3fd0..71eb8e5 100644
--- a/ash/webui/camera_app_ui/resources/images/images.gni
+++ b/ash/webui/camera_app_ui/resources/images/images.gni
@@ -30,7 +30,6 @@
   "camera_mode_scan.svg",
   "camera_mode_video.svg",
   "camera_shutter_photo_start.svg",
-  "camera_shutter_photo_stop_hover.svg",
   "camera_shutter_photo_stop.svg",
   "camera_shutter_video_pause.svg",
   "camera_shutter_video_snapshot.svg",
diff --git a/ash/webui/camera_app_ui/resources/js/device/preview.ts b/ash/webui/camera_app_ui/resources/js/device/preview.ts
index 147833d..9593020 100644
--- a/ash/webui/camera_app_ui/resources/js/device/preview.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/preview.ts
@@ -65,6 +65,11 @@
   private faceOverlay: FaceOverlay|null = null;
 
   /**
+   * The observer to monitor average FPS of the preview stream.
+   */
+  private fpsObserver: util.FpsObserver|null = null;
+
+  /**
    * Current active stream.
    */
   private streamInternal: MediaStream|null = null;
@@ -534,30 +539,13 @@
     const resolution = `${videoWidth}x${videoHeight}`;
     const videoTrack = this.getVideoTrack();
     const deviceName = videoTrack.label;
-
-    // Currently there is no easy way to calculate the fps of a video element.
-    // Here we use the metadata events to calculate a reasonable approximation.
-    const updateFps = (() => {
-      const FPS_MEASURE_FRAMES = 100;
-      const timestamps: number[] = [];
-      return () => {
-        const now = performance.now();
-        timestamps.push(now);
-        if (timestamps.length > FPS_MEASURE_FRAMES) {
-          timestamps.shift();
-        }
-        if (timestamps.length === 1) {
-          return null;
-        }
-        return (timestamps.length - 1) / (now - timestamps[0]) * 1000;
-      };
-    })();
-
     const deviceOperator = DeviceOperator.getInstance();
     if (deviceOperator === null) {
       return;
     }
 
+    this.fpsObserver = new util.FpsObserver(this.video);
+
     const {deviceId} = getVideoTrackSettings(videoTrack);
     const activeArraySize = await deviceOperator.getActiveArraySize(deviceId);
     const cameraFrameRotation =
@@ -586,9 +574,11 @@
     const callback = (metadata: CameraMetadata) => {
       showValue('#preview-resolution', resolution);
       showValue('#preview-device-name', deviceName);
-      const fps = updateFps();
-      if (fps !== null) {
-        showValue('#preview-fps', `${fps.toFixed(0)} FPS`);
+      if (this.fpsObserver !== null) {
+        const fps = this.fpsObserver.getAverageFps();
+        if (fps !== null) {
+          showValue('#preview-fps', `${fps.toFixed(0)} FPS`);
+        }
       }
 
       let faceMode = AndroidStatisticsFaceDetectMode
@@ -653,6 +643,11 @@
       this.faceOverlay.clear();
       this.faceOverlay = null;
     }
+
+    if (this.fpsObserver !== null) {
+      this.fpsObserver.stop();
+      this.fpsObserver = null;
+    }
   }
 
   /**
diff --git a/ash/webui/camera_app_ui/resources/js/test/cca_test.ts b/ash/webui/camera_app_ui/resources/js/test/cca_test.ts
index 5dca1b30..556acfd8 100644
--- a/ash/webui/camera_app_ui/resources/js/test/cca_test.ts
+++ b/ash/webui/camera_app_ui/resources/js/test/cca_test.ts
@@ -14,7 +14,7 @@
 import {DeviceOperator} from '../mojo/device_operator.js';
 import * as state from '../state.js';
 import {Facing, Mode, Resolution} from '../type.js';
-import {assertEnumVariant, sleep} from '../util.js';
+import {assertEnumVariant, FpsObserver, sleep} from '../util.js';
 import {windowController} from '../window_controller.js';
 
 import {
@@ -520,4 +520,8 @@
     const component = SETTING_OPTION_MAP[option].component;
     CCATest.click(component);
   }
+
+  static getFpsObserver(): FpsObserver {
+    return new FpsObserver(getPreviewVideo());
+  }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/util.ts b/ash/webui/camera_app_ui/resources/js/util.ts
index 97cfab29..65ebf56 100644
--- a/ash/webui/camera_app_ui/resources/js/util.ts
+++ b/ash/webui/camera_app_ui/resources/js/util.ts
@@ -478,3 +478,37 @@
   }
   return {minFps, maxFps};
 }
+
+// Observer to monitor the average FPS of preview within an interval.
+export class FpsObserver {
+  private readonly timestamps: number[] = [];
+
+  private callbackId = 0;
+
+  constructor(private readonly videoElement: HTMLVideoElement) {
+    const FPS_MEASUREMENT_MAX_SAMPLE_COUNT = 100;
+    const updateFps = () => {
+      this.timestamps.push(performance.now());
+      if (this.timestamps.length > FPS_MEASUREMENT_MAX_SAMPLE_COUNT) {
+        this.timestamps.shift();
+      }
+      this.callbackId = this.videoElement.requestVideoFrameCallback(updateFps);
+    };
+    this.callbackId = this.videoElement.requestVideoFrameCallback(updateFps);
+  }
+
+  // Returns the average FPS according to the collected timestamps. If the
+  // amount of data is not enough, returns null instead.
+  getAverageFps(): number|null {
+    if (this.timestamps.length <= 1) {
+      return null;
+    }
+    return (this.timestamps.length - 1) /
+        (this.timestamps[this.timestamps.length - 1] - this.timestamps[0]) *
+        1000;
+  }
+
+  stop(): void {
+    this.videoElement.cancelVideoFrameCallback(this.callbackId);
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/views/main.html b/ash/webui/camera_app_ui/resources/views/main.html
index a17657d9..fb87369c 100644
--- a/ash/webui/camera_app_ui/resources/views/main.html
+++ b/ash/webui/camera_app_ui/resources/views/main.html
@@ -242,7 +242,8 @@
                 i18n-label="take_photo_button"
                 data-svg="camera_shutter_photo_start.svg"></button>
         <button id="stop-takephoto" class="shutter inkdrop" tabindex="0"
-                i18n-label="take_photo_cancel_button"></button>
+                i18n-label="take_photo_cancel_button"
+                data-svg="camera_shutter_photo_stop.svg"></button>
       </div>
       <div id="pause-recordvideo-holder" class="buttons right-stripe circle">
         <button id="pause-recordvideo" tabindex="0"
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
index 1bd2e8a5..c8ccff0 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
@@ -93,6 +93,14 @@
     --personalization-app-grid-item-background-color: var(--google-grey-200);
   }
 
+  :host-context(body.jelly-enabled) wallpaper-grid-item[is-local-no-images]::part(item) {
+    background: linear-gradient(var(--cros-sys-primary), var(--cros-sys-primary_container));
+  }
+
+  :host-context(body.jelly-enabled) wallpaper-grid-item[is-local-no-images]::part(image) {
+    display: none;
+  }
+
   wallpaper-grid-item[data-is-time-of-day-collection] {
     --personalization-app-grid-item-border-radius: 80px;
   }
@@ -144,6 +152,7 @@
               collage
               disabled="[[!isSelectableTile_(item)]]"
               index="[[index]]"
+              is-local-no-images$="[[isLocalNoImagesTile_(item)]]"
               on-wallpaper-grid-item-selected="onCollectionSelected_"
               primary-text="[[item.name]]"
               role="option"
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.ts b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.ts
index 2799859..fea135c 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.ts
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.ts
@@ -151,6 +151,7 @@
   }
 
   if (!localImages || localImages.length === 0) {
+    // TODO(b/282050032): After Jelly is launched, remove the preview image.
     return {
       count: getCountText(0),
       disabled: true,
@@ -485,6 +486,10 @@
     return !!item && !this.isLoadingTile_(item) && !item.disabled;
   }
 
+  private isLocalNoImagesTile_(item: Tile): boolean {
+    return !!item && this.isLocalTile_(item) && item.count === getCountText(0);
+  }
+
   private isTimeOfDayCollection_(item: Tile|null): boolean {
     return this.isOnlineTile_(item) &&
         item.id === loadTimeData.getString('timeOfDayWallpaperCollectionId');
diff --git a/ash/webui/print_management/print_management_ui.cc b/ash/webui/print_management/print_management_ui.cc
index 9784185..80c804b 100644
--- a/ash/webui/print_management/print_management_ui.cc
+++ b/ash/webui/print_management/print_management_ui.cc
@@ -83,6 +83,8 @@
       {"stopped", IDS_PRINT_MANAGEMENT_STOPPED_ERROR_STATUS},
       {"clientUnauthorized",
        IDS_PRINT_MANAGEMENT_CLIENT_UNAUTHORIZED_ERROR_STATUS},
+      {"expiredCertificate",
+       IDS_PRINT_MANAGEMENT_EXPIRED_CERTIFICATE_ERROR_STATUS},
       {"filterFailed", IDS_PRINT_MANAGEMENT_FILTERED_FAILED_ERROR_STATUS},
       {"unknownPrinterError", IDS_PRINT_MANAGEMENT_UNKNOWN_ERROR_STATUS},
       {"paperJamStopped", IDS_PRINT_MANAGEMENT_PAPER_JAM_STOPPED_ERROR_STATUS},
diff --git a/ash/webui/print_management/resources/print_job_entry.ts b/ash/webui/print_management/resources/print_job_entry.ts
index 1ab3fbc..a7c248b 100644
--- a/ash/webui/print_management/resources/print_job_entry.ts
+++ b/ash/webui/print_management/resources/print_job_entry.ts
@@ -506,6 +506,8 @@
         return loadTimeData.getString('unknownPrinterError');
       case PrinterErrorCode.kClientUnauthorized:
         return loadTimeData.getString('clientUnauthorized');
+      case PrinterErrorCode.kExpiredCertificate:
+        return loadTimeData.getString('expiredCertificate');
       default:
         assertNotReached();
     }
@@ -541,6 +543,8 @@
         return loadTimeData.getString('unknownPrinterErrorStopped');
       case PrinterErrorCode.kClientUnauthorized:
         return loadTimeData.getString('clientUnauthorized');
+      case PrinterErrorCode.kExpiredCertificate:
+        return loadTimeData.getString('expiredCertificate');
       case PrinterErrorCode.kPrinterUnreachable:
         assertNotReached();
       default:
diff --git a/ash/webui/print_management/resources/print_management_shared.css b/ash/webui/print_management/resources/print_management_shared.css
index d21b8a78..184a6b40 100644
--- a/ash/webui/print_management/resources/print_management_shared.css
+++ b/ash/webui/print_management/resources/print_management_shared.css
@@ -10,6 +10,12 @@
  * #include=cr-shared-style cros-color-overrides
  * #css_wrapper_metadata_end */
 
+:host {
+  /* Margin on hover for responsive layout adjusts based on padded-left minus
+     16px to align left. */
+  --active-status-hover-margin: calc(var(--padded-left) - 16px);
+}
+
 :host-context(body.jelly-enabled) .main-container {
   display: flex;
   flex-flow: column;
@@ -135,9 +141,16 @@
 
 #activeStatusContainer:hover {
   background-color: rgba(var(--google-blue-600-rgb), 0.06);
+  border-radius: 16px;
+  margin-inline-start: 16px;
+  padding-inline-start: 16px;
 }
 
 @media (min-width: 600px) and (max-width: 767px) {
+  :host {
+    --padded-left: 32px;
+  }
+
   :host-context(body.jelly-enabled) #headerContainer {
     margin-inline: 20px;
   }
@@ -163,7 +176,7 @@
   }
 
   .padded-left {
-    margin-inline-start: 32px;
+    margin-inline-start: var(--padded-left);
   }
 
   .printer-name-column {
@@ -175,13 +188,15 @@
   }
 
   #activeStatusContainer:hover {
-    border-radius: 16px;
-    margin-inline-start: 16px;
-    padding-inline-start: 16px;
+    margin-inline-start: var(--active-status-hover-margin);
   }
 }
 
 @media (min-width: 768px) and (max-width: 959px) {
+  :host {
+    --padded-left: 40px;
+  }
+
   :host-context(body.jelly-enabled) #headerContainer {
     margin-inline: 20px;
   }
@@ -207,7 +222,7 @@
   }
 
   .padded-left {
-    margin-inline-start: 40px;
+    margin-inline-start: var(--padded-left);
   }
 
   .printer-name-column {
@@ -219,13 +234,15 @@
   }
 
   #activeStatusContainer:hover {
-    border-radius: 16px;
-    margin-inline-start: 16px;
-    padding-inline-start: 16px;
+    margin-inline-start: var(--active-status-hover-margin);
   }
 }
 
 @media (min-width: 960px) and (max-width: 1279px) {
+  :host {
+    --padded-left: 48px;
+  }
+
   :host-context(body.jelly-enabled) #headerContainer {
     margin-inline: 32px;
   }
@@ -251,7 +268,7 @@
   }
 
   .padded-left {
-    margin-inline-start: 48px;
+    margin-inline-start: var(--padded-left);
   }
 
   .printer-name-column {
@@ -263,13 +280,15 @@
   }
 
   #activeStatusContainer:hover {
-    border-radius: 16px;
-    margin-inline-start: 16px;
-    padding-inline-start: 16px;
+    margin-inline-start: var(--active-status-hover-margin);
   }
 }
 
 @media (min-width: 1280px) {
+  :host {
+    --padded-left: 56px;
+  }
+
   :host-context(body.jelly-enabled) #headerContainer {
     margin-inline: 72px;
   }
@@ -295,7 +314,7 @@
   }
 
   .padded-left {
-    margin-inline-start: 56px;
+    margin-inline-start: var(--padded-left);
   }
 
   .printer-name-column {
@@ -307,8 +326,6 @@
   }
 
   #activeStatusContainer:hover {
-    border-radius: 16px;
-    margin-inline-start: 16px;
-    padding-inline-start: 16px;
+    margin-inline-start: var(--active-status-hover-margin);
   }
 }
diff --git a/ash/webui/projector_app/projector_message_handler.cc b/ash/webui/projector_app/projector_message_handler.cc
index cefbf9c7..8c266ccf 100644
--- a/ash/webui/projector_app/projector_message_handler.cc
+++ b/ash/webui/projector_app/projector_message_handler.cc
@@ -16,52 +16,11 @@
 #include "base/functional/bind.h"
 #include "base/json/values_util.h"
 #include "base/time/time.h"
-#include "components/signin/public/identity_manager/access_token_info.h"
 #include "content/public/browser/web_ui.h"
 #include "url/gurl.h"
 
 namespace ash {
 
-namespace {
-
-// Response keys.
-constexpr char kUserEmail[] = "email";
-constexpr char kToken[] = "token";
-constexpr char kExpirationTime[] = "expirationTime";
-constexpr char kError[] = "error";
-constexpr char kOAuthTokenInfo[] = "oauthTokenInfo";
-
-// Projector Error Strings.
-constexpr char kNoneStr[] = "NONE";
-constexpr char kOtherStr[] = "OTHER";
-constexpr char kTokenFetchFailureStr[] = "TOKEN_FETCH_FAILURE";
-
-// Struct used to describe args to set user's preference.
-struct SetUserPrefArgs {
-  std::string pref_name;
-  base::Value value;
-};
-
-base::Value::Dict AccessTokenInfoToValue(const signin::AccessTokenInfo& info) {
-  base::Value::Dict value;
-  value.Set(kToken, info.token);
-  value.Set(kExpirationTime, base::TimeToValue(info.expiration_time));
-  return value;
-}
-
-std::string ProjectorErrorToString(ProjectorError mode) {
-  switch (mode) {
-    case ProjectorError::kNone:
-      return kNoneStr;
-    case ProjectorError::kTokenFetchFailure:
-      return kTokenFetchFailureStr;
-    case ProjectorError::kOther:
-      return kOtherStr;
-  }
-}
-
-}  // namespace
-
 ProjectorMessageHandler::ProjectorMessageHandler() = default;
 ProjectorMessageHandler::~ProjectorMessageHandler() = default;
 
@@ -71,63 +30,10 @@
 
 void ProjectorMessageHandler::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
-      "getOAuthTokenForAccount",
-      base::BindRepeating(&ProjectorMessageHandler::GetOAuthTokenForAccount,
-                          base::Unretained(this)));
-
-  web_ui()->RegisterMessageCallback(
-      "onError", base::BindRepeating(&ProjectorMessageHandler::OnError,
-                                     base::Unretained(this)));
-
-  web_ui()->RegisterMessageCallback(
       "getVideo", base::BindRepeating(&ProjectorMessageHandler::GetVideo,
                                       base::Unretained(this)));
 }
 
-void ProjectorMessageHandler::GetOAuthTokenForAccount(
-    const base::Value::List& args) {
-  // Two arguments. The first is callback id, and the second is the list
-  // containing the account for which to fetch the oauth token.
-  CHECK_EQ(args.size(), 2u);
-  const auto& oauth_token_fetch_callback = args[0].GetString();
-  const auto& requested_account = args[1].GetList();
-
-  CHECK_EQ(requested_account.size(), 1u);
-  const std::string& email = requested_account[0].GetString();
-
-  oauth_token_fetcher_.GetAccessTokenFor(
-      email,
-      base::BindOnce(&ProjectorMessageHandler::OnAccessTokenRequestCompleted,
-                     GetWeakPtr(), oauth_token_fetch_callback));
-}
-
-void ProjectorMessageHandler::OnError(const base::Value::List& args) {
-  // TODO(b/195113693): Get the SWA dialog associated with this WebUI and close
-  // it.
-}
-
-void ProjectorMessageHandler::OnAccessTokenRequestCompleted(
-    const std::string& js_callback_id,
-    const std::string& email,
-    GoogleServiceAuthError error,
-    const signin::AccessTokenInfo& info) {
-  AllowJavascript();
-
-  base::Value::Dict response;
-  response.Set(kUserEmail, base::Value(email));
-  if (error.state() != GoogleServiceAuthError::State::NONE) {
-    response.Set(kOAuthTokenInfo, base::Value());
-    response.Set(kError, base::Value(ProjectorErrorToString(
-                             ProjectorError::kTokenFetchFailure)));
-  } else {
-    response.Set(kError,
-                 base::Value(ProjectorErrorToString(ProjectorError::kNone)));
-    response.Set(kOAuthTokenInfo, AccessTokenInfoToValue(info));
-  }
-
-  ResolveJavascriptCallback(base::Value(js_callback_id), response);
-}
-
 void ProjectorMessageHandler::GetVideo(const base::Value::List& args) {
   // Two arguments. The first is callback id, and the second is the list
   // containing the item id and resource key.
diff --git a/ash/webui/projector_app/projector_message_handler.h b/ash/webui/projector_app/projector_message_handler.h
index 4951384..956e21c 100644
--- a/ash/webui/projector_app/projector_message_handler.h
+++ b/ash/webui/projector_app/projector_message_handler.h
@@ -14,21 +14,10 @@
 #include "content/public/browser/web_ui_message_handler.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 
-namespace signin {
-struct AccessTokenInfo;
-}  // namespace signin
-
 namespace ash {
 
 struct ProjectorScreencastVideo;
 
-// Enum to record the different errors that may occur in the Projector app.
-enum class ProjectorError {
-  kNone = 0,
-  kOther,
-  kTokenFetchFailure,
-};
-
 // Handles messages from the Projector WebUIs (i.e. chrome://projector).
 class ProjectorMessageHandler : public content::WebUIMessageHandler {
  public:
@@ -46,22 +35,6 @@
   void set_web_ui_for_test(content::WebUI* web_ui) { set_web_ui(web_ui); }
 
  private:
-  // Requested by the Projector SWA to get access to the OAuth token for the
-  // account email provided in the `args`.
-  void GetOAuthTokenForAccount(const base::Value::List& args);
-
-  // Called by the Projector SWA when an error occurred.
-  void OnError(const base::Value::List& args);
-
-  // Called when OAuth token fetch request is completed by
-  // ProjectorOAuthTokenFetcher. Resolves the javascript promise created by
-  // ProjectorBrowserProxy.getOAuthTokenForAccount by calling the
-  // `js_callback_id`.
-  void OnAccessTokenRequestCompleted(const std::string& js_callback_id,
-                                     const std::string& email,
-                                     GoogleServiceAuthError error,
-                                     const signin::AccessTokenInfo& info);
-
   // Requested by the Projector SWA to fetch a single video from DriveFS with
   // the Drive item id specified by `args`.
   void GetVideo(const base::Value::List& args);
@@ -73,8 +46,6 @@
                       std::unique_ptr<ProjectorScreencastVideo> video,
                       const std::string& error_message);
 
-  ProjectorOAuthTokenFetcher oauth_token_fetcher_;
-
   base::WeakPtrFactory<ProjectorMessageHandler> weak_ptr_factory_{this};
 };
 
diff --git a/ash/webui/projector_app/resources/app/trusted/projector_browser_proxy.js b/ash/webui/projector_app/resources/app/trusted/projector_browser_proxy.js
index 08e14e5..8b2a6584 100644
--- a/ash/webui/projector_app/resources/app/trusted/projector_browser_proxy.js
+++ b/ash/webui/projector_app/resources/app/trusted/projector_browser_proxy.js
@@ -12,21 +12,6 @@
  */
 export class ProjectorBrowserProxy {
   /**
-   * Gets the oauth token with the required scopes for the specified account.
-   * @param {string} email, user's email.
-   * @return {!Promise<!projectorApp.OAuthToken>}
-   */
-  getOAuthTokenForAccount(email) {}
-
-  /**
-   * Sends 'error' message to handler.
-   * The Handler will log the message. If the error is not a recoverable error,
-   * the handler closes the corresponding WebUI.
-   * @param {!Array<string>} msg Error messages.
-   */
-  onError(msg) {}
-
-  /**
    * Gets information about the specified video from DriveFS.
    * @param {string} videoFileId The Drive item id of the video file.
    * @param {string|undefined} resourceKey The Drive item resource key.
@@ -55,16 +40,6 @@
   }
 
   /** @override */
-  getOAuthTokenForAccount(email) {
-    return sendWithPromise('getOAuthTokenForAccount', [email]);
-  }
-
-  /** @override */
-  onError(msg) {
-    return chrome.send('onError', msg);
-  }
-
-  /** @override */
   getVideo(videoFileId, resourceKey) {
     return sendWithPromise('getVideo', [videoFileId, resourceKey]);
   }
diff --git a/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js b/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js
index 9d0e3ff..4a199ed 100644
--- a/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js
+++ b/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js
@@ -45,15 +45,6 @@
     super(iframeElement, TARGET_URL, TARGET_URL);
     this.browserProxy_ = browserProxy;
 
-    this.registerMethod('getOAuthTokenForAccount', (args) => {
-      if (!args || args.length != 1) {
-        return Promise.reject('Incorrect args for getOAuthTokenForAccount');
-      }
-      return this.browserProxy_.getOAuthTokenForAccount(args[0]);
-    });
-    this.registerMethod('onError', (msg) => {
-      this.browserProxy_.onError(msg);
-    });
     this.registerMethod('getVideo', (args) => {
       if (!args || args.length != 2) {
         return Promise.reject('Incorrect args for getVideo');
diff --git a/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js b/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js
index 3f42f12..b8540da 100644
--- a/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js
+++ b/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js
@@ -91,8 +91,7 @@
    * @return {!Promise<!projectorApp.OAuthToken>}
    */
   getOAuthTokenForAccount(account) {
-    return AppUntrustedCommFactory.getPostMessageAPIClient().callApiFn(
-        'getOAuthTokenForAccount', [account]);
+    return Promise.reject('Unsupported method getOauthTokenForAccount');
   },
 
   /**
@@ -102,8 +101,7 @@
    * @param {!Array<ProjectorError>} msg Error messages.
    */
   onError(msg) {
-    AppUntrustedCommFactory.getPostMessageAPIClient().callApiFn(
-        'onError', [msg]);
+    console.error('Received error messages:', msg);
   },
 
   /**
diff --git a/ash/webui/projector_app/test/projector_message_handler_unittest.cc b/ash/webui/projector_app/test/projector_message_handler_unittest.cc
index 126d82a..e70e2d85 100644
--- a/ash/webui/projector_app/test/projector_message_handler_unittest.cc
+++ b/ash/webui/projector_app/test/projector_message_handler_unittest.cc
@@ -23,12 +23,10 @@
 
 using ::testing::_;
 
-const char kTestUserEmail[] = "testuser1@gmail.com";
 const char kVideoFileId[] = "video_file_id";
 const char kResourceKey[] = "resource_key";
 
 const char kWebUIResponse[] = "cr.webUIResponse";
-const char kGetOAuthTokenCallback[] = "getOAuthTokenCallback";
 const char kGetVideoCallback[] = "getVideoCallback";
 
 }  // namespace
@@ -71,25 +69,6 @@
   content::TestWebUI web_ui_;
 };
 
-TEST_F(ProjectorMessageHandlerUnitTest, GetOAuthTokenForAccount) {
-  mock_app_client().SetAutomaticIssueOfAccessTokens(false);
-
-  base::Value::List list_args;
-  list_args.Append(kGetOAuthTokenCallback);
-  base::Value::List args;
-  args.Append(kTestUserEmail);
-  list_args.Append(std::move(args));
-
-  web_ui().HandleReceivedMessage("getOAuthTokenForAccount", list_args);
-  mock_app_client().WaitForAccessRequest(kTestUserEmail);
-
-  EXPECT_EQ(web_ui().call_data().size(), 1u);
-
-  const content::TestWebUI::CallData& call_data = FetchCallData(0);
-  EXPECT_EQ(call_data.function_name(), kWebUIResponse);
-  EXPECT_EQ(call_data.arg1()->GetString(), kGetOAuthTokenCallback);
-}
-
 TEST_F(ProjectorMessageHandlerUnitTest, GetVideo) {
   ProjectorScreencastVideo expected_video;
   expected_video.file_id = kVideoFileId;
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
index edbe38b..bc2805b 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
@@ -52,6 +52,8 @@
 using HiddenAcceleratorMap =
     std::map<AcceleratorActionId, std::vector<ui::Accelerator>>;
 
+constexpr size_t kMaxAcceleratorsAllowed = 5;
+
 // Raw accelerator data may result in the same shortcut being displayed multiple
 // times in the frontend. GetHiddenAcceleratorMap() is used to collect such
 // accelerators and hide them from display.
@@ -484,6 +486,16 @@
     return;
   }
 
+  // Only allow a maximum of five accelerators per action.
+  const size_t accelerator_count =
+      ash_accelerator_configuration_->GetAcceleratorsForAction(action_id)
+          .size();
+  if (accelerator_count >= kMaxAcceleratorsAllowed) {
+    result_data->result = AcceleratorConfigResult::kMaximumAcceleratorsReached;
+    std::move(callback).Run(std::move(result_data));
+    return;
+  }
+
   absl::optional<AcceleratorResultDataPtr> result_data_ptr =
       PreprocessAddAccelerator(source, action_id, accelerator);
   // Check if there was an error during processing the accelerator, if so return
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
index b21e8f7..70c0f3e 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
@@ -1425,6 +1425,38 @@
   EXPECT_EQ(mojom::AcceleratorConfigResult::kKeyNotAllowed, result->result);
 }
 
+TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorExceedsMaximum) {
+  // Initialize default accelerators.
+  const AcceleratorData test_data[] = {
+      {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kToggleMirrorMode},
+      {/*trigger_on_press=*/true, ui::VKEY_A, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kToggleMirrorMode},
+      {/*trigger_on_press=*/true, ui::VKEY_S, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kToggleMirrorMode},
+      {/*trigger_on_press=*/true, ui::VKEY_D, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kToggleMirrorMode},
+      {/*trigger_on_press=*/true, ui::VKEY_F, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kToggleMirrorMode},
+  };
+
+  AshAcceleratorConfiguration* config =
+      Shell::Get()->ash_accelerator_configuration();
+  config->Initialize(test_data);
+  base::RunLoop().RunUntilIdle();
+
+  AcceleratorResultDataPtr result;
+  // Attempting to add a 6th accelerator will result in an error.
+  const ui::Accelerator accelerator(ui::VKEY_M, ui::EF_CONTROL_DOWN);
+  ash::shortcut_customization::mojom::
+      AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
+          .AddAccelerator(mojom::AcceleratorSource::kAsh,
+                          AcceleratorAction::kToggleMirrorMode, accelerator,
+                          &result);
+  EXPECT_EQ(mojom::AcceleratorConfigResult::kMaximumAcceleratorsReached,
+            result->result);
+}
+
 TEST_F(AcceleratorConfigurationProviderTest, ReservedKeysNotAllowed) {
   const AcceleratorData test_data[] = {
       {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
index 5ad670f..7e6baaf 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
@@ -294,6 +294,12 @@
         this.hasError = true;
         return;
       }
+      case AcceleratorConfigResult.kMaximumAcceleratorsReached: {
+        // TODO(jimmyxgong): Localize this message.
+        this.statusMessage = 'Maximum accelerators have reached.';
+        this.hasError = true;
+        return;
+      }
       case AcceleratorConfigResult.kSuccess: {
         this.pendingAcceleratorInfo = createEmptyAcceleratorInfo();
         this.fireUpdateEvent();
diff --git a/ash/webui/web_applications/test/sandboxed_web_ui_test_base.cc b/ash/webui/web_applications/test/sandboxed_web_ui_test_base.cc
index 3ef4bea6..89a9ea25 100644
--- a/ash/webui/web_applications/test/sandboxed_web_ui_test_base.cc
+++ b/ash/webui/web_applications/test/sandboxed_web_ui_test_base.cc
@@ -80,10 +80,7 @@
                    owner_->scripts_.end());
 
     for (const auto& script : scripts) {
-      // Use ExecuteScript(), not ExecJs(), because of Content Security Policy
-      // directive: "script-src chrome://resources 'self'"
-      ASSERT_TRUE(
-          content::ExecuteScript(guest_frame, LoadJsTestLibrary(script)));
+      ASSERT_TRUE(content::ExecJs(guest_frame, LoadJsTestLibrary(script)));
     }
     if (!owner_->test_module_.empty()) {
       constexpr char kScript[] = R"(
@@ -94,7 +91,7 @@
             document.body.appendChild(s);
           })();
       )";
-      ASSERT_TRUE(content::ExecuteScript(
+      ASSERT_TRUE(content::ExecJs(
           guest_frame, base::ReplaceStringPlaceholders(
                            kScript, {owner_->test_module_}, nullptr)));
     }
diff --git a/ash/wm/desks/desk_animation_impl.h b/ash/wm/desks/desk_animation_impl.h
index 1f8673d9..b40a94d 100644
--- a/ash/wm/desks/desk_animation_impl.h
+++ b/ash/wm/desks/desk_animation_impl.h
@@ -10,6 +10,7 @@
 #include "ash/wm/desks/desk_animation_base.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 
diff --git a/ash/wm/desks/desk_bar_view_base.h b/ash/wm/desks/desk_bar_view_base.h
index 32d8cff..b5bdbf0 100644
--- a/ash/wm/desks/desk_bar_view_base.h
+++ b/ash/wm/desks/desk_bar_view_base.h
@@ -281,7 +281,7 @@
 
   State state_ = State::kZero;
 
-  // True if the `DesksBarBoundsAnimation` is started and hasn't finished yet.
+  // True if the `DeskBarBoundsAnimation` is started and hasn't finished yet.
   // It will be used to hold `Layout` until the bounds animation is completed.
   // `Layout` is expensive and will be called on bounds changes, which means it
   // will be called lots of times during the bounds changes animation. This is
diff --git a/ash/wm/desks/desk_mini_view_animations.cc b/ash/wm/desks/desk_mini_view_animations.cc
index 4e6b8c5..ab1eb13 100644
--- a/ash/wm/desks/desk_mini_view_animations.cc
+++ b/ash/wm/desks/desk_mini_view_animations.cc
@@ -12,7 +12,6 @@
 #include "ash/wm/desks/desk_mini_view.h"
 #include "ash/wm/desks/desks_constants.h"
 #include "ash/wm/desks/expanded_desks_bar_button.h"
-#include "ash/wm/desks/legacy_desk_bar_view.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_session.h"
@@ -59,8 +58,8 @@
 constexpr base::TimeDelta kLabelFadeInDelay = base::Milliseconds(100);
 constexpr base::TimeDelta kLabelFadeInDuration = base::Milliseconds(50);
 
-// |settings| will be initialized with a fast-out-slow-in animation with the
-// given |duration|.
+// `settings` will be initialized with a fast-out-slow-in animation with the
+// given `duration`.
 void InitScopedAnimationSettings(ui::ScopedLayerAnimationSettings* settings,
                                  base::TimeDelta duration) {
   settings->SetTransitionDuration(duration);
@@ -72,8 +71,8 @@
       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
 }
 
-// Animates the transform of the layer of the given |view| from the supplied
-// |begin_transform| to the identity transform.
+// Animates the transform of the layer of the given `view` from the supplied
+// `begin_transform` to the identity transform.
 void AnimateView(views::View* view, const gfx::Transform& begin_transform) {
   ui::Layer* layer = view->layer();
   layer->SetTransform(begin_transform);
@@ -116,9 +115,9 @@
   }
 }
 
-// Gets the scale transform for |view|, it can be scale up or scale down. The
-// anchor of the scale animation will be a point whose |x| is the center of the
-// desks bar while |y| is the top of the given |view|. GetMirroredX is used here
+// Gets the scale transform for `view`, it can be scale up or scale down. The
+// anchor of the scale animation will be a point whose `x` is the center of the
+// desk bar while `y` is the top of the given `view`. GetMirroredX is used here
 // to make sure the transform is correct while in RTL layout.
 gfx::Transform GetScaleTransformForView(views::View* view, int bar_x_center) {
   return gfx::GetScaleTransform(
@@ -126,7 +125,7 @@
       kEnterOrExitZeroStateScale);
 }
 
-// Scales down the given |view| to |kEnterOrExitZeroStateScale| and fading out
+// Scales down the given `view` to `kEnterOrExitZeroStateScale` and fading out
 // it at the same time.
 void ScaleDownAndFadeOutView(views::View* view, int bar_x_center) {
   ui::Layer* layer = view->layer();
@@ -137,7 +136,7 @@
   layer->SetOpacity(0.f);
 }
 
-// Scales up the given |view| from |kEnterOrExitZeroStateScale| to identity and
+// Scales up the given `view` from `kEnterOrExitZeroStateScale` to identity and
 // fading in it at the same time.
 void ScaleUpAndFadeInView(views::View* view, int bar_x_center) {
   DCHECK(view);
@@ -168,16 +167,16 @@
 }
 
 // A self-deleting object that performs a fade out animation on
-// |removed_mini_view|'s layer by changing its opacity from 1 to 0 and scales
-// down it around the center of |bar_view| while switching back to zero state.
-// |removed_mini_view_| and the object itself will be deleted when the
+// `removed_mini_view`'s layer by changing its opacity from 1 to 0 and scales
+// down it around the center of `bar_view` while switching back to zero state.
+// `removed_mini_view_` and the object itself will be deleted when the
 // animation is complete.
 // TODO(afakhry): Consider generalizing HidingWindowAnimationObserverBase to be
 // reusable for the mini_view removal animation.
 class RemovedMiniViewAnimation : public ui::ImplicitAnimationObserver {
  public:
   RemovedMiniViewAnimation(DeskMiniView* removed_mini_view,
-                           LegacyDeskBarView* bar_view,
+                           DeskBarViewBase* bar_view,
                            const bool to_zero_state)
       : removed_mini_view_(removed_mini_view), bar_view_(bar_view) {
     removed_mini_view_->set_is_animating_to_remove(true);
@@ -215,35 +214,35 @@
 
  private:
   raw_ptr<DeskMiniView, ExperimentalAsh> removed_mini_view_;
-  const raw_ptr<LegacyDeskBarView, ExperimentalAsh> bar_view_;
+  const raw_ptr<DeskBarViewBase, ExperimentalAsh> bar_view_;
 };
 
-// A self-deleting object that performs bounds changes animation for the desks
+// A self-deleting object that performs bounds changes animation for the desk
 // bar while it switches between zero state and expanded state.
 // `is_bounds_animation_on_going_` will be used to help hold Layout calls during
 // the animation. Since Layout is expensive and will be called lots of times
 // during the bounds changes animation without doing this. The object itself
 // will be deleted when the animation is complete.
-class DesksBarBoundsAnimation : public ui::ImplicitAnimationObserver {
+class DeskBarBoundsAnimation : public ui::ImplicitAnimationObserver {
  public:
-  DesksBarBoundsAnimation(LegacyDeskBarView* bar_view, bool to_zero_state)
+  DeskBarBoundsAnimation(DeskBarViewBase* bar_view, bool to_zero_state)
       : bar_view_(bar_view) {
     auto* desks_widget = bar_view_->GetWidget();
     const gfx::Rect current_widget_bounds =
         desks_widget->GetWindowBoundsInScreen();
     gfx::Rect target_widget_bounds = current_widget_bounds;
-    // When `to_zero_state` is false, desks bar is switching from zero to
+    // When `to_zero_state` is false, desk bar is switching from zero to
     // expanded state.
     if (to_zero_state) {
       target_widget_bounds.set_height(kDeskBarZeroStateHeight);
 
       if (chromeos::features::IsJellyrollEnabled()) {
-        // When `Jellyroll` is enabled, setting desks bar's bounds to its bounds
+        // When `Jellyroll` is enabled, setting desk bar's bounds to its bounds
         // at zero state directly to layout its contents at the correct position
         // first before the animation. When `Jellyroll` is enabled, we use the
         // same buttons (default desk button and library) for both expanded
         // state and zero state, the scale up and fade in animation is applied
-        // to the buttona during the desks bar states transition, thus the
+        // to the buttona during the desk bar states transition, thus the
         // buttons need to be layout and put at the correct positions before the
         // animation starts.
         desks_widget->SetBounds(target_widget_bounds);
@@ -253,13 +252,13 @@
         bar_view_->set_is_bounds_animation_on_going(true);
       }
     } else {
-      // While switching desks bar from zero state to expanded state, setting
+      // While switching desk bar from zero state to expanded state, setting
       // its bounds to its bounds at expanded state directly without animation,
       // which will trigger Layout and make sure the contents of
-      // desks bar(e.g, desk mini view, new desk button) are at the correct
+      // desk bar(e.g, desk mini view, new desk button) are at the correct
       // positions before the animation. And set `is_bounds_animation_on_going_`
       // to be true, which will help hold Layout until the animation is done.
-      // Then set the bounds of the desks bar back to its bounds at zero state
+      // Then set the bounds of the desk bar back to its bounds at zero state
       // to start the bounds change animation. See more details at
       // `is_bounds_animation_on_going_`.
       target_widget_bounds.set_height(DeskBarViewBase::GetPreferredBarHeight(
@@ -281,15 +280,15 @@
     desks_widget->SetBounds(target_widget_bounds);
   }
 
-  DesksBarBoundsAnimation(const DesksBarBoundsAnimation&) = delete;
-  DesksBarBoundsAnimation& operator=(const DesksBarBoundsAnimation&) = delete;
+  DeskBarBoundsAnimation(const DeskBarBoundsAnimation&) = delete;
+  DeskBarBoundsAnimation& operator=(const DeskBarBoundsAnimation&) = delete;
 
-  ~DesksBarBoundsAnimation() override {
+  ~DeskBarBoundsAnimation() override {
     DCHECK(bar_view_);
     bar_view_->set_is_bounds_animation_on_going(false);
     if (Shell::Get()->overview_controller()->InOverviewSession()) {
-      // Updated the desk buttons and layout the desks bar to make sure the
-      // buttons visibility will be updated on desks bar state changes. Also
+      // Updated the desk buttons and layout the desk bar to make sure the
+      // buttons visibility will be updated on desk bar state changes. Also
       // make sure the button's text will be updated correctly while going back
       // to zero state.
       bar_view_->UpdateDeskButtonsVisibility();
@@ -302,7 +301,7 @@
   void OnImplicitAnimationsCompleted() override { delete this; }
 
  private:
-  const raw_ptr<LegacyDeskBarView, ExperimentalAsh> bar_view_;
+  const raw_ptr<DeskBarViewBase, ExperimentalAsh> bar_view_;
 };
 
 // A self-deleting class that performs the scale up / down animation for the
@@ -385,7 +384,7 @@
 }  // namespace
 
 void PerformNewDeskMiniViewAnimation(
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     std::vector<DeskMiniView*> new_mini_views,
     std::vector<DeskMiniView*> mini_views_left,
     std::vector<DeskMiniView*> mini_views_right,
@@ -425,7 +424,7 @@
   AnimateMiniViews(mini_views_left, mini_views_left_begin_transform);
   AnimateMiniViews(mini_views_right, mini_views_right_begin_transform);
 
-  // The new desk button and the library button in the expanded desks bar
+  // The new desk button and the library button in the expanded desk bar
   // always move to the right when a new desk is added.
   const auto& button_transform = base::i18n::IsRTL()
                                      ? mini_views_left_begin_transform
@@ -451,7 +450,7 @@
 }
 
 void PerformRemoveDeskMiniViewAnimation(
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     DeskMiniView* removed_mini_view,
     std::vector<DeskMiniView*> mini_views_left,
     std::vector<DeskMiniView*> mini_views_right,
@@ -491,8 +490,8 @@
 }
 
 void PerformZeroStateToExpandedStateMiniViewAnimation(
-    LegacyDeskBarView* bar_view) {
-  new DesksBarBoundsAnimation(bar_view, /*to_zero_state=*/false);
+    DeskBarViewBase* bar_view) {
+  new DeskBarBoundsAnimation(bar_view, /*to_zero_state=*/false);
   const int bar_x_center = bar_view->bounds().CenterPoint().x();
   for (auto* mini_view : bar_view->mini_views())
     ScaleUpAndFadeInView(mini_view, bar_x_center);
@@ -507,7 +506,7 @@
 }
 
 void PerformZeroStateToExpandedStateMiniViewAnimationCrOSNext(
-    LegacyDeskBarView* bar_view) {
+    DeskBarViewBase* bar_view) {
   bar_view->new_desk_button()->UpdateState(
       CrOSNextDeskIconButton::State::kExpanded);
   auto* library_button = bar_view->library_button();
@@ -523,7 +522,7 @@
     }
   }
 
-  new DesksBarBoundsAnimation(bar_view, /*to_zero_state=*/false);
+  new DeskBarBoundsAnimation(bar_view, /*to_zero_state=*/false);
 
   const int bar_x_center = bar_view->bounds().CenterPoint().x();
   for (auto* mini_view : bar_view->mini_views()) {
@@ -533,7 +532,7 @@
   ScaleUpAndFadeInView(bar_view->new_desk_button(), bar_x_center);
   if (library_button) {
     ScaleUpAndFadeInView(library_button, bar_x_center);
-    // Library button could be at active state when the desks bar is switched
+    // Library button could be at active state when the desk bar is switched
     // from the zero state to the expanded state, for example when clicking on
     // the library button at zero state. Thus we should also try to fade in the
     // library button label here.
@@ -546,11 +545,11 @@
 }
 
 void PerformExpandedStateToZeroStateMiniViewAnimation(
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     std::vector<DeskMiniView*> removed_mini_views) {
   for (auto* mini_view : removed_mini_views)
     new RemovedMiniViewAnimation(mini_view, bar_view, /*to_zero_state=*/true);
-  new DesksBarBoundsAnimation(bar_view, /*to_zero_state=*/true);
+  new DeskBarBoundsAnimation(bar_view, /*to_zero_state=*/true);
   const gfx::Rect bounds = bar_view->bounds();
   ScaleDownAndFadeOutView(bar_view->expanded_state_new_desk_button(),
                           bounds.CenterPoint().x());
@@ -584,7 +583,7 @@
   const int start_index = move_right ? old_index : new_index + 1;
   const int end_index = move_right ? new_index : old_index + 1;
 
-  // Since |old_index| and |new_index| are unequal valid indices, there
+  // Since `old_index` and `new_index` are unequal valid indices, there
   // must be at least two desks.
   int shift_x = mini_views[0]->GetMirroredBounds().x() -
                 mini_views[1]->GetMirroredBounds().x();
@@ -629,7 +628,7 @@
 
 void PerformDeskIconButtonScaleAnimationCrOSNext(
     CrOSNextDeskIconButton* button,
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     const gfx::Transform& new_desk_button_rects_transform,
     int shift_x) {
   new DeskIconButtonScaleAnimation(button, new_desk_button_rects_transform);
diff --git a/ash/wm/desks/desk_mini_view_animations.h b/ash/wm/desks/desk_mini_view_animations.h
index 40e9214..c8095ed 100644
--- a/ash/wm/desks/desk_mini_view_animations.h
+++ b/ash/wm/desks/desk_mini_view_animations.h
@@ -19,7 +19,7 @@
 namespace ash {
 
 class CrOSNextDeskIconButton;
-class LegacyDeskBarView;
+class DeskBarViewBase;
 class DeskMiniView;
 class ExpandedDesksBarButton;
 
@@ -27,7 +27,7 @@
 // desk bar view. It will also animate existing desks to show them moving as a
 // result of creating the new mini_views. `new_mini_views` contains a list of
 // the newly-created mini_views. `mini_views_left` are the mini views on the
-// left of the new mini views in the desks bar, while `mini_views_right` are the
+// left of the new mini views in the desk bar, while `mini_views_right` are the
 // mini views on the right side of the new mini views.
 // The new desk button and the library button (if it exists) will be moved to
 // the right. `shift_x` is the amount by which the mini_views (new and existing)
@@ -38,16 +38,16 @@
 //   mini_views (new and existing) have already been laid out in their final
 //   positions.
 void PerformNewDeskMiniViewAnimation(
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     std::vector<DeskMiniView*> new_mini_views,
     std::vector<DeskMiniView*> mini_views_left,
     std::vector<DeskMiniView*> mini_views_right,
     int shift_x);
 
 // Performs the mini_view removal animation. It is in charge of removing the
-// |removed_mini_view| from the views hierarchy and deleting it. We also update
-// the |bar_view| desk buttons visibility once the animation completes.
-// |mini_views_left|, and |mini_views_right| are lists of the remaining
+// `removed_mini_view` from the views hierarchy and deleting it. We also update
+// the `bar_view` desk buttons visibility once the animation completes.
+// `mini_views_left`, and `mini_views_right` are lists of the remaining
 // mini_views to left and to the right of the removed mini_view respectively.
 // The new desk button will be moved to right the same as `mini_views_right`. If
 // the library button is non-null, it will also be moved to the right the same
@@ -58,49 +58,49 @@
 // have been laid out in their final positions as if the removed mini_view no
 // longer exists.
 void PerformRemoveDeskMiniViewAnimation(
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     DeskMiniView* removed_mini_view,
     std::vector<DeskMiniView*> mini_views_left,
     std::vector<DeskMiniView*> mini_views_right,
     int shift_x);
 
-// Performs the animation of switching from zero state desks bar to expanded
-// state desks bar. It scales up and fades in the current mini views and the
-// ExpandedDesksBarButton. Also animates the desks bar view from the zero state
+// Performs the animation of switching from zero state desk bar to expanded
+// state desk bar. It scales up and fades in the current mini views and the
+// ExpandedDesksBarButton. Also animates the desk bar view from the zero state
 // bar's height to the expanded bar's height.
 void PerformZeroStateToExpandedStateMiniViewAnimation(
-    LegacyDeskBarView* bar_view);
+    DeskBarViewBase* bar_view);
 
 void PerformZeroStateToExpandedStateMiniViewAnimationCrOSNext(
-    LegacyDeskBarView* bar_view);
+    DeskBarViewBase* bar_view);
 
-// Performs the animation of switching from expanded state desks bar to zero
-// state desks bar. This happens when a desk is removed such that a single desk
-// is remaining. It scales down and fades out the |removed_mini_views| and the
-// ExpandedDesksBarButton. |removed_mini_views| will be removed from the
+// Performs the animation of switching from expanded state desk bar to zero
+// state desk bar. This happens when a desk is removed such that a single desk
+// is remaining. It scales down and fades out the `removed_mini_views` and the
+// ExpandedDesksBarButton. `removed_mini_views` will be removed from the
 // views hierarchy. But the ExpandedDesksBarButton will be kept and set to
-// invisible. It will also animate the desks bar view from the expanded bar's
+// invisible. It will also animate the desk bar view from the expanded bar's
 // height to zero state bar's height.
 //
 // * Notes:
-// - It assumes |removed_mini_views| and the ExpandedDesksBarButton are still
+// - It assumes `removed_mini_views` and the ExpandedDesksBarButton are still
 //   laid out at their previous positions before the bar state transition.
 // - Layout will be done once the animation is completed.
 void PerformExpandedStateToZeroStateMiniViewAnimation(
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     std::vector<DeskMiniView*> removed_mini_views);
 
 // Performs the mini_view reorder animation. It moves the desks to make space at
-// |new_index| for the mini_view at |old_index|. Before reordering, if
-// |old_index| < |new_index|, the mini views from |old_index| + 1 to
-// |new_index| would move left; if |old_index| > |new_index|, the mini
-// views from |new_index| to |old_index| - 1 would move right.
+// `new_index` for the mini_view at `old_index`. Before reordering, if
+// `old_index` < `new_index`, the mini views from `old_index` + 1 to
+// `new_index` would move left; if `old_index` > `new_index`, the mini
+// views from `new_index` to `old_index` - 1 would move right.
 //
-// Note that the |mini_views| is the reordered list. Therefore, the range of the
+// Note that the `mini_views` is the reordered list. Therefore, the range of the
 // mini views to be moved should be selected according to the current position.
-// If |old_index| < |new_index|, the range is from |old_index| to
-// |new_index| - 1; otherwise, the range is from |new_index| + 1 to
-// |old_index|.
+// If `old_index` < `new_index`, the range is from `old_index` to
+// `new_index` - 1; otherwise, the range is from `new_index` + 1 to
+// `old_index`.
 void PerformReorderDeskMiniViewAnimation(
     int old_index,
     int new_index,
@@ -125,7 +125,7 @@
 // button have been laid out in their final positions.
 void PerformDeskIconButtonScaleAnimationCrOSNext(
     CrOSNextDeskIconButton* button,
-    LegacyDeskBarView* bar_view,
+    DeskBarViewBase* bar_view,
     const gfx::Transform& new_desk_button_rects_transform,
     int shift_x);
 
diff --git a/ash/wm/float/float_controller.h b/ash/wm/float/float_controller.h
index d082362..6b9d1204e 100644
--- a/ash/wm/float/float_controller.h
+++ b/ash/wm/float/float_controller.h
@@ -12,6 +12,7 @@
 #include "ash/rotator/screen_rotation_animator_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/wm/desks/desks_controller.h"
+#include "base/gtest_prod_util.h"
 #include "base/scoped_multi_source_observation.h"
 #include "base/scoped_observation.h"
 #include "chromeos/ui/base/window_state_type.h"
diff --git a/ash/wm/float/tablet_mode_tuck_education.h b/ash/wm/float/tablet_mode_tuck_education.h
index 219040e8..66ab21d 100644
--- a/ash/wm/float/tablet_mode_tuck_education.h
+++ b/ash/wm/float/tablet_mode_tuck_education.h
@@ -6,6 +6,7 @@
 #define ASH_WM_FLOAT_TABLET_MODE_TUCK_EDUCATION_H_
 
 #include "ash/ash_export.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/clock.h"
 #include "components/prefs/pref_registry_simple.h"
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue.h b/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
index f7f57c6..25c534f 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/wm/window_state_observer.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/timer/timer.h"
diff --git a/ash/wm/workspace/workspace_window_resizer.h b/ash/wm/workspace/workspace_window_resizer.h
index 3340ee7..a8a8b8f2 100644
--- a/ash/wm/workspace/workspace_window_resizer.h
+++ b/ash/wm/workspace/workspace_window_resizer.h
@@ -12,6 +12,7 @@
 
 #include "ash/wm/window_resizer.h"
 #include "ash/wm/workspace/magnetism_matcher.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
diff --git a/base/BUILD.gn b/base/BUILD.gn
index a663d31..939fb298 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2046,6 +2046,7 @@
   # Mac or iOS.
   if (is_apple) {
     sources += [
+      "apple/bridging.h",
       "file_version_info_mac.h",
       "file_version_info_mac.mm",
       "files/file_util_mac.mm",
@@ -2141,7 +2142,6 @@
       "ios/ns_range.h",
       "ios/scoped_critical_action.h",
       "ios/scoped_critical_action.mm",
-      "mac/bridging.h",
       "native_library_ios.mm",
       "power_monitor/power_monitor_device_source_ios.mm",
       "process/process_metrics_ios.cc",
diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc
index 8f2a358..b731e385 100644
--- a/base/allocator/partition_alloc_features.cc
+++ b/base/allocator/partition_alloc_features.cc
@@ -155,8 +155,6 @@
      "disabled-but-2-way-split-with-memory-reclaimer"},
     {BackupRefPtrMode::kDisabledButSplitPartitions3Way,
      "disabled-but-3-way-split"},
-    {BackupRefPtrMode::kDisabledButAddDummyRefCount,
-     "disabled-but-add-dummy-ref-count"},
 };
 
 const base::FeatureParam<BackupRefPtrMode> kBackupRefPtrModeParam{
diff --git a/base/allocator/partition_alloc_features.h b/base/allocator/partition_alloc_features.h
index 69796418..95c3391 100644
--- a/base/allocator/partition_alloc_features.h
+++ b/base/allocator/partition_alloc_features.h
@@ -104,11 +104,6 @@
   // BRP is disabled, but the main partition *and* aligned partition are split
   // out, as if BRP was enabled in the "before allocation" mode.
   kDisabledButSplitPartitions3Way,
-
-  //  BRP is disabled, but add dummy ref count to each allocation. This will
-  // increase allocation size but not change any of the logic. If an issue
-  // reproduce in this mode, it means the increase in size is causing it.
-  kDisabledButAddDummyRefCount,
 };
 
 enum class AlternateBucketDistributionMode : uint8_t {
diff --git a/base/allocator/partition_alloc_support.cc b/base/allocator/partition_alloc_support.cc
index ed89278..0aa0758 100644
--- a/base/allocator/partition_alloc_support.cc
+++ b/base/allocator/partition_alloc_support.cc
@@ -358,9 +358,6 @@
       case features::BackupRefPtrMode::kDisabledButSplitPartitions3Way:
         brp_group_name = "DisabledBut3WaySplit";
         break;
-      case features::BackupRefPtrMode::kDisabledButAddDummyRefCount:
-        brp_group_name = "DisabledButAddDummyRefCount";
-        break;
     }
 
     if (features::kBackupRefPtrModeParam.Get() !=
@@ -899,7 +896,6 @@
   bool enable_brp_zapping = false;
   bool split_main_partition = false;
   bool use_dedicated_aligned_partition = false;
-  bool add_dummy_ref_count = false;
   bool process_affected_by_brp_flag = false;
   bool enable_memory_reclaimer = false;
 
@@ -975,14 +971,6 @@
         split_main_partition = true;
         use_dedicated_aligned_partition = true;
         break;
-
-      case base::features::BackupRefPtrMode::kDisabledButAddDummyRefCount:
-        split_main_partition = true;
-        add_dummy_ref_count = true;
-#if !BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
-        use_dedicated_aligned_partition = true;
-#endif
-        break;
     }
   }
 #endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) &&
@@ -997,10 +985,13 @@
                         base::features::kPartitionAllocBackupRefPtrForAsh);
 #endif
 
-  return {enable_brp,           enable_brp_for_ash,
-          enable_brp_zapping,   enable_memory_reclaimer,
-          split_main_partition, use_dedicated_aligned_partition,
-          add_dummy_ref_count,  process_affected_by_brp_flag};
+  return {enable_brp,
+          enable_brp_for_ash,
+          enable_brp_zapping,
+          enable_memory_reclaimer,
+          split_main_partition,
+          use_dedicated_aligned_partition,
+          process_affected_by_brp_flag};
 }
 
 void PartitionAllocSupport::ReconfigureEarlyish(
@@ -1142,7 +1133,6 @@
       allocator_shim::SplitMainPartition(brp_config.split_main_partition),
       allocator_shim::UseDedicatedAlignedPartition(
           brp_config.use_dedicated_aligned_partition),
-      allocator_shim::AddDummyRefCount(brp_config.add_dummy_ref_count),
       allocator_shim::AlternateBucketDistribution(bucket_distribution));
 #endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
 
diff --git a/base/allocator/partition_alloc_support.h b/base/allocator/partition_alloc_support.h
index 6421047..73ed28e 100644
--- a/base/allocator/partition_alloc_support.h
+++ b/base/allocator/partition_alloc_support.h
@@ -50,7 +50,6 @@
     bool enable_brp_partition_memory_reclaimer = false;
     bool split_main_partition = false;
     bool use_dedicated_aligned_partition = false;
-    bool add_dummy_ref_count = false;
     bool process_affected_by_brp_flag = false;
   };
   // Reconfigure* functions re-configure PartitionAlloc. It is impossible to
diff --git a/base/allocator/partition_allocator/BUILD.gn b/base/allocator/partition_allocator/BUILD.gn
index 152e1c7..1bded1d 100644
--- a/base/allocator/partition_allocator/BUILD.gn
+++ b/base/allocator/partition_allocator/BUILD.gn
@@ -429,7 +429,6 @@
     "ENABLE_BACKUP_REF_PTR_SUPPORT=$enable_backup_ref_ptr_support",
     "ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=$enable_backup_ref_ptr_slow_checks",
     "ENABLE_BACKUP_REF_PTR_FEATURE_FLAG=$enable_backup_ref_ptr_feature_flag",
-    "ENABLE_RAW_PTR_EXPERIMENTAL=$enable_raw_ptr_experimental",
     "ENABLE_DANGLING_RAW_PTR_CHECKS=$enable_dangling_raw_ptr_checks",
     "ENABLE_DANGLING_RAW_PTR_FEATURE_FLAG=$enable_dangling_raw_ptr_feature_flag",
     "ENABLE_DANGLING_RAW_PTR_PERF_EXPERIMENT=$enable_dangling_raw_ptr_perf_experiment",
diff --git a/base/allocator/partition_allocator/address_pool_manager.cc b/base/allocator/partition_allocator/address_pool_manager.cc
index 47e545f2..3604507 100644
--- a/base/allocator/partition_allocator/address_pool_manager.cc
+++ b/base/allocator/partition_allocator/address_pool_manager.cc
@@ -18,6 +18,7 @@
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/allocator/partition_allocator/partition_alloc_notreached.h"
 #include "base/allocator/partition_allocator/reservation_offset_table.h"
+#include "base/allocator/partition_allocator/thread_isolation/alignment.h"
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_APPLE) || BUILDFLAG(ENABLE_THREAD_ISOLATION)
@@ -548,4 +549,17 @@
   }
 }
 
+#if BUILDFLAG(ENABLE_THREAD_ISOLATION)
+// This function just exists to static_assert the layout of the private fields
+// in Pool.
+void AddressPoolManager::AssertThreadIsolatedLayout() {
+  constexpr size_t last_pool_offset =
+      offsetof(AlignedPools, pools_) + sizeof(Pool) * (kNumPools - 1);
+  constexpr size_t alloc_bitset_offset =
+      last_pool_offset + offsetof(Pool, alloc_bitset_);
+  static_assert(alloc_bitset_offset % PA_THREAD_ISOLATED_ALIGN_SZ == 0);
+  static_assert(sizeof(AlignedPools) % PA_THREAD_ISOLATED_ALIGN_SZ == 0);
+}
+#endif  // BUILDFLAG(ENABLE_THREAD_ISOLATION)
+
 }  // namespace partition_alloc::internal
diff --git a/base/allocator/partition_allocator/address_pool_manager.h b/base/allocator/partition_allocator/address_pool_manager.h
index 7e59851..9791158 100644
--- a/base/allocator/partition_allocator/address_pool_manager.h
+++ b/base/allocator/partition_allocator/address_pool_manager.h
@@ -112,7 +112,13 @@
   // if PartitionAlloc is wholly unused in this process.)
   bool GetStats(AddressSpaceStats* stats);
 
+#if BUILDFLAG(ENABLE_THREAD_ISOLATION)
+  static void AssertThreadIsolatedLayout();
+#endif  // BUILDFLAG(ENABLE_THREAD_ISOLATION)
+
 #if BUILDFLAG(HAS_64_BIT_POINTERS)
+
+  struct AlignedPools;
   class Pool {
    public:
     constexpr Pool() = default;
@@ -136,6 +142,12 @@
     void GetStats(PoolStats* stats);
 
    private:
+    // The lock needs to be the first field in this class.
+    // We write-protect the pool in the ThreadIsolated case, except that the
+    // lock can be used without acquiring write-permission first (via
+    // DumpStats()). So instead of protecting the whole variable, we only
+    // protect the memory after the lock.
+    // See the alignment of `aligned_pools_` below.
     Lock lock_;
 
     // The bitset stores the allocation state of the address pool. 1 bit per
@@ -153,6 +165,11 @@
 #if BUILDFLAG(PA_DCHECK_IS_ON)
     uintptr_t address_end_ = 0;
 #endif
+
+#if BUILDFLAG(ENABLE_THREAD_ISOLATION)
+    friend void AddressPoolManager::AssertThreadIsolatedLayout();
+    friend struct AlignedPools;
+#endif  // BUILDFLAG(ENABLE_THREAD_ISOLATION)
   };
 
   PA_ALWAYS_INLINE Pool* GetPool(pool_handle handle) {
@@ -167,10 +184,16 @@
   // If thread isolation support is enabled, we need to write-protect the
   // isolated pool (which needs to be last). For this, we need to add padding in
   // front of the pools so that the isolated one starts on a page boundary.
-  struct {
-    char pad_[PA_THREAD_ISOLATED_ARRAY_PAD_SZ(Pool, kNumPools)] = {};
+  // We also skip the Lock at the beginning of the pool since it needs to be
+  // used in contexts where we didn't enable write access to the pool memory.
+  struct AlignedPools {
+    char pad_[PA_THREAD_ISOLATED_ARRAY_PAD_SZ_WITH_OFFSET(
+        Pool,
+        kNumPools,
+        offsetof(Pool, alloc_bitset_))] = {};
     Pool pools_[kNumPools];
-    char pad_after_[PA_THREAD_ISOLATED_FILL_PAGE_SZ(sizeof(Pool))] = {};
+    char pad_after_[PA_THREAD_ISOLATED_FILL_PAGE_SZ(
+        sizeof(Pool) - offsetof(Pool, alloc_bitset_))] = {};
   } aligned_pools_ PA_THREAD_ISOLATED_ALIGN;
 
 #endif  // BUILDFLAG(HAS_64_BIT_POINTERS)
diff --git a/base/allocator/partition_allocator/partition_alloc.gni b/base/allocator/partition_allocator/partition_alloc.gni
index cd5f217..4f512dba5 100644
--- a/base/allocator/partition_allocator/partition_alloc.gni
+++ b/base/allocator/partition_allocator/partition_alloc.gni
@@ -95,25 +95,6 @@
   force_enable_raw_ptr_exclusion = false
 }
 
-declare_args() {
-  # Determines whether `raw_ptr_experimental<T>` is an alias for
-  # `raw_ptr<T>` or `T*` (true raw pointer).
-  #
-  # Members rewritten as `raw_ptr_experimental` rely on this as an
-  # escape hatch to degrade to a `T*` if `raw_ptr` performance proves
-  # problematic. Defaults to match standard `raw_ptr` support.
-  #
-  # One side effect of this is that `raw_ptr_experimental<T> foo_` must
-  # not use `foo_.get()`; this is incoherent when `foo_` is a `T*`.
-  # Use `base::to_address()` instead.
-  #
-  # Analogously, the same applies to `raw_ref_experimental<T>` and `T&`.
-  # However, as references are a sort of exotic arcana, a nonstandard
-  # `base::GetRawReference()` is provided in `raw_ref.h`. This fans out
-  # to `raw_ref::get()` or a no-op `T&` reflection as necessary.
-  enable_raw_ptr_experimental = enable_backup_ref_ptr_support
-}
-
 assert(!enable_pointer_compression_support || glue_core_pools,
        "Pointer compression relies on core pools being contiguous.")
 
@@ -215,7 +196,6 @@
 if (!use_partition_alloc) {
   use_partition_alloc_as_malloc = false
   enable_backup_ref_ptr_support = false
-  enable_raw_ptr_experimental = false
   use_asan_backup_ref_ptr = false
   use_asan_unowned_ptr = false
   use_hookable_raw_ptr = false
@@ -239,10 +219,6 @@
 assert(enable_backup_ref_ptr_support || !enable_backup_ref_ptr_slow_checks,
        "Can't enable additional BackupRefPtr checks if it isn't enabled at all")
 
-assert(
-    enable_backup_ref_ptr_support || !enable_raw_ptr_experimental,
-    "Can't make `raw_ptr_experimental` = `raw_ptr` when the latter is wholly disabled")
-
 # enable_dangling_raw_ptr_checks can only be used if enable_backup_ref_ptr_support
 # is true.
 assert(
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index 56604004..985c1eb 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -330,7 +330,6 @@
         partition_alloc::PartitionOptions::BackupRefPtr::kDisabled,
         partition_alloc::PartitionOptions::BackupRefPtrZapping::kDisabled,
         partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
-        partition_alloc::PartitionOptions::AddDummyRefCount::kDisabled,
         partition_alloc::ThreadIsolationOption(pkey_),
     });
     if (UseThreadIsolatedPool() && pkey_ != kInvalidPkey) {
@@ -342,7 +341,6 @@
           partition_alloc::PartitionOptions::BackupRefPtr::kDisabled,
           partition_alloc::PartitionOptions::BackupRefPtrZapping::kDisabled,
           partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
-          partition_alloc::PartitionOptions::AddDummyRefCount::kDisabled,
           partition_alloc::ThreadIsolationOption(pkey_),
       });
       return;
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index 7b7ae95..ffec432 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -969,7 +969,7 @@
   }
 
   if (PA_LIKELY(slot_size <= kMaxMemoryTaggingSize &&
-                root->memory_tagging_enabled())) {
+                root->IsMemoryTaggingEnabled())) {
     // Ensure the MTE-tag of the memory pointed by |return_slot| is unguessable.
     TagMemoryRangeRandomly(return_slot, slot_size);
   }
diff --git a/base/allocator/partition_allocator/partition_lock.h b/base/allocator/partition_allocator/partition_lock.h
index 79d3cdb..f549c73 100644
--- a/base/allocator/partition_allocator/partition_lock.h
+++ b/base/allocator/partition_allocator/partition_lock.h
@@ -66,6 +66,9 @@
 
   void Release() PA_UNLOCK_FUNCTION() {
 #if BUILDFLAG(PA_DCHECK_IS_ON)
+#if BUILDFLAG(ENABLE_THREAD_ISOLATION)
+    LiftThreadIsolationScope lift_thread_isolation_restrictions;
+#endif
     owning_thread_ref_.store(base::PlatformThreadRef(),
                              std::memory_order_release);
 #endif
diff --git a/base/allocator/partition_allocator/partition_root.cc b/base/allocator/partition_allocator/partition_root.cc
index b0e131e..c3e9124 100644
--- a/base/allocator/partition_allocator/partition_root.cc
+++ b/base/allocator/partition_allocator/partition_root.cc
@@ -905,18 +905,6 @@
          PartitionOptions::UseConfigurablePool::kIfAvailable) &&
         IsConfigurablePoolAvailable();
     PA_DCHECK(!flags.use_configurable_pool || IsConfigurablePoolAvailable());
-#if PA_CONFIG(HAS_MEMORY_TAGGING)
-    TagViolationReportingMode memory_tagging_mode =
-        internal::GetMemoryTaggingModeForCurrentThread();
-    // Memory tagging is not supported in the configurable pool because MTE
-    // stores tagging information in the high bits of the pointer, it causes
-    // issues with components like V8's ArrayBuffers which use custom pointer
-    // representations. All custom representations encountered so far rely on an
-    // "is in configurable pool?" check, so we use that as a proxy.
-    flags.memory_tagging_enabled_ =
-        !flags.use_configurable_pool &&
-        memory_tagging_mode != TagViolationReportingMode::kUndefined;
-#endif
 
     // brp_enabled() is not supported in the configurable pool because
     // BRP requires objects to be in a different Pool.
@@ -953,13 +941,6 @@
       flags.extras_size += internal::kPartitionRefCountSizeAdjustment;
       flags.extras_offset += internal::kPartitionRefCountOffsetAdjustment;
     }
-    if (opts.add_dummy_ref_count ==
-        PartitionOptions::AddDummyRefCount::kEnabled) {
-      // AddDummyRefCount will increase the size to simulate adding
-      // PartitionRefCount, but non of the BRP logic will run.
-      PA_CHECK(!brp_enabled());
-      flags.extras_size += internal::kPartitionRefCountSizeAdjustment;
-    }
 #endif  // PA_CONFIG(EXTRAS_REQUIRED)
 
     // Re-confirm the above PA_CHECKs, by making sure there are no
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h
index 56c7bfc..2038355 100644
--- a/base/allocator/partition_allocator/partition_root.h
+++ b/base/allocator/partition_allocator/partition_root.h
@@ -180,11 +180,6 @@
     kEnabled,
   };
 
-  enum class AddDummyRefCount : uint8_t {
-    kDisabled,
-    kEnabled,
-  };
-
   enum class UseConfigurablePool : uint8_t {
     kNo,
     kIfAvailable,
@@ -198,8 +193,7 @@
       Cookie cookie,
       BackupRefPtr backup_ref_ptr,
       BackupRefPtrZapping backup_ref_ptr_zapping,
-      UseConfigurablePool use_configurable_pool,
-      AddDummyRefCount add_dummy_ref_count = AddDummyRefCount::kDisabled
+      UseConfigurablePool use_configurable_pool
 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
       ,
       ThreadIsolationOption thread_isolation = ThreadIsolationOption()
@@ -226,7 +220,6 @@
   BackupRefPtr backup_ref_ptr;
   BackupRefPtrZapping backup_ref_ptr_zapping;
   UseConfigurablePool use_configurable_pool;
-  AddDummyRefCount add_dummy_ref_count = AddDummyRefCount::kDisabled;
 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
   ThreadIsolationOption thread_isolation;
 #endif
@@ -290,9 +283,6 @@
 #endif  // PA_CONFIG(ENABLE_MAC11_MALLOC_SIZE_HACK)
 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
     bool use_configurable_pool;
-#if PA_CONFIG(HAS_MEMORY_TAGGING)
-    bool memory_tagging_enabled_;
-#endif
 
 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
     ThreadIsolationOption thread_isolation;
@@ -552,6 +542,8 @@
   PA_ALWAYS_INLINE size_t
   AllocationCapacityFromRequestedSize(size_t size) const;
 
+  PA_ALWAYS_INLINE bool IsMemoryTaggingEnabled() const;
+
   // Frees memory from this partition, if possible, by decommitting pages or
   // even entire slot spans. |flags| is an OR of base::PartitionPurgeFlags.
   void PurgeMemory(int flags);
@@ -677,7 +669,7 @@
     // If quarantine is enabled and the tag overflows, move the containing slot
     // to quarantine, to prevent the attacker from exploiting a pointer that has
     // an old tag.
-    if (PA_LIKELY(memory_tagging_enabled())) {
+    if (PA_LIKELY(IsMemoryTaggingEnabled())) {
       return internal::HasOverflowTag(object);
     }
     // Default behaviour if MTE is not enabled for this PartitionRoot.
@@ -811,19 +803,6 @@
     return flags.use_configurable_pool;
   }
 
-  // Returns whether MTE is supported for this partition root. Because MTE
-  // stores tagging information in the high bits of the pointer, it causes
-  // issues with components like V8's ArrayBuffers which use custom pointer
-  // representations. All custom representations encountered so far rely on an
-  // "is in configurable pool?" check, so we use that as a proxy.
-  bool memory_tagging_enabled() const {
-#if PA_CONFIG(HAS_MEMORY_TAGGING)
-    return flags.memory_tagging_enabled_;
-#else
-    return false;
-#endif
-  }
-
   // To make tests deterministic, it is necessary to uncap the amount of memory
   // waste incurred by empty slot spans. Otherwise, the size of various
   // freelists, and committed memory becomes harder to reason about (and
@@ -1209,6 +1188,21 @@
   FreeNoHooks(object);
 }
 
+// Returns whether MTE is supported for this partition root. Because MTE stores
+// tagging information in the high bits of the pointer, it causes issues with
+// components like V8's ArrayBuffers which use custom pointer representations.
+// All custom representations encountered so far rely on an "is in configurable
+// pool?" check, so we use that as a proxy.
+template <bool thread_safe>
+PA_ALWAYS_INLINE bool PartitionRoot<thread_safe>::IsMemoryTaggingEnabled()
+    const {
+#if PA_CONFIG(HAS_MEMORY_TAGGING)
+  return !flags.use_configurable_pool;
+#else
+  return false;
+#endif
+}
+
 // static
 template <bool thread_safe>
 PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeNoHooks(void* object) {
@@ -1253,7 +1247,7 @@
   PA_DCHECK(slot_span == SlotSpan::FromSlotStart(slot_start));
 
 #if PA_CONFIG(HAS_MEMORY_TAGGING)
-  if (PA_LIKELY(root->memory_tagging_enabled())) {
+  if (PA_LIKELY(root->IsMemoryTaggingEnabled())) {
     const size_t slot_size = slot_span->bucket->slot_size;
     if (PA_LIKELY(slot_size <= internal::kMaxMemoryTaggingSize)) {
       // slot_span is untagged at this point, so we have to recover its tag
@@ -1759,7 +1753,7 @@
   PageAccessibilityConfiguration::Permissions permissions =
       PageAccessibilityConfiguration::kReadWrite;
 #if PA_CONFIG(HAS_MEMORY_TAGGING)
-  if (memory_tagging_enabled()) {
+  if (IsMemoryTaggingEnabled()) {
     permissions = PageAccessibilityConfiguration::kReadWriteTagged;
   }
 #endif
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr.h b/base/allocator/partition_allocator/pointers/raw_ptr.h
index cd378902..cb2af20 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr.h
+++ b/base/allocator/partition_allocator/pointers/raw_ptr.h
@@ -168,14 +168,6 @@
 template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
 class raw_ptr;
 
-#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
-using raw_ptr_experimental = raw_ptr<T, Traits>;
-#else
-template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
-using raw_ptr_experimental = T*;
-#endif  // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-
 }  // namespace base
 
 // This type is to be used internally, or in callbacks arguments when it is
@@ -1156,7 +1148,6 @@
 }  // namespace base
 
 using base::raw_ptr;
-using base::raw_ptr_experimental;
 
 // DisableDanglingPtrDetection option for raw_ptr annotates
 // "intentional-and-safe" dangling pointers. It is meant to be used at the
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc b/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc
index 7644478..6bf9689 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc
+++ b/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc
@@ -1499,37 +1499,6 @@
   EXPECT_EQ(base::to_address(raw), base::to_address(miracle));
 }
 
-// Verifies that `raw_ptr_experimental` is aliased appropriately.
-//
-// The `DisableDanglingPtrDetection` trait is arbitrarily chosen and is
-// just there to ensure that `raw_ptr_experimental` knows how to field
-// the traits template argument.
-#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-static_assert(
-    std::is_same_v<raw_ptr_experimental<int, DisableDanglingPtrDetection>,
-                   raw_ptr<int, DisableDanglingPtrDetection>>);
-static_assert(
-    std::is_same_v<raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
-                   raw_ptr<const int, DisableDanglingPtrDetection>>);
-static_assert(
-    std::is_same_v<
-        const raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
-        const raw_ptr<const int, DisableDanglingPtrDetection>>);
-#else   // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-// `DisableDanglingPtrDetection` means nothing here and is silently
-// ignored.
-static_assert(
-    std::is_same_v<raw_ptr_experimental<int, DisableDanglingPtrDetection>,
-                   int*>);
-static_assert(
-    std::is_same_v<raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
-                   const int*>);
-static_assert(
-    std::is_same_v<
-        const raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
-        const int* const>);
-#endif  // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-
 }  // namespace
 
 namespace base {
diff --git a/base/allocator/partition_allocator/pointers/raw_ref.h b/base/allocator/partition_allocator/pointers/raw_ref.h
index 92a9356..f2841c2 100644
--- a/base/allocator/partition_allocator/pointers/raw_ref.h
+++ b/base/allocator/partition_allocator/pointers/raw_ref.h
@@ -375,31 +375,9 @@
 template <typename T>
 using RemoveRawRefT = typename RemoveRawRef<T>::type;
 
-#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
-using raw_ref_experimental = raw_ref<T, Traits>;
-#else
-template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
-using raw_ref_experimental = T&;
-#endif  // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-
-// Accepts a `raw_ref_experimental<T>` and unconditionally turns it
-// into a T&. This is necessary to provide the escape hatch for easily
-// downgrading `raw_ref_experimental` (see `partition_alloc.gni`).
-template <typename T>
-T& GetRawReference(raw_ref_experimental<T> miracle_ref) {
-#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-  return miracle_ref.get();
-#else
-  static_assert(std::is_same_v<decltype(miracle_ref), T&>);
-  return miracle_ref;
-#endif  // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-}
-
 }  // namespace base
 
 using base::raw_ref;
-using base::raw_ref_experimental;
 
 template <base::RawPtrTraits Traits = base::RawPtrTraits::kEmpty, typename T>
 auto ToRawRef(T& ref) {
diff --git a/base/allocator/partition_allocator/pointers/raw_ref_unittest.cc b/base/allocator/partition_allocator/pointers/raw_ref_unittest.cc
index f800854..9993cf3 100644
--- a/base/allocator/partition_allocator/pointers/raw_ref_unittest.cc
+++ b/base/allocator/partition_allocator/pointers/raw_ref_unittest.cc
@@ -954,45 +954,6 @@
               CountersMatch());
 }
 
-// Verifies that `raw_ref_experimental` is aliased appropriately.
-//
-// The `DisableDanglingPtrDetection` trait is arbitrarily chosen and is
-// just there to ensure that `raw_ref_experimental` knows how to field
-// the traits template argument.
-#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-static_assert(
-    std::is_same_v<raw_ref_experimental<int, DisableDanglingPtrDetection>,
-                   raw_ref<int, DisableDanglingPtrDetection>>);
-static_assert(
-    std::is_same_v<raw_ref_experimental<const int, DisableDanglingPtrDetection>,
-                   raw_ref<const int, DisableDanglingPtrDetection>>);
-static_assert(
-    std::is_same_v<
-        const raw_ref_experimental<const int, DisableDanglingPtrDetection>,
-        const raw_ref<const int, DisableDanglingPtrDetection>>);
-#else   // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-// `DisableDanglingPtrDetection` means nothing here and is silently
-// ignored.
-static_assert(
-    std::is_same_v<raw_ref_experimental<int, DisableDanglingPtrDetection>,
-                   int&>);
-static_assert(
-    std::is_same_v<raw_ref_experimental<const int, DisableDanglingPtrDetection>,
-                   const int&>);
-
-// `const raw_ref` is a thing, but `T& const` is not. Therefore, when
-// `raw_ref_experimental` is `T&`, we cannot utter the type
-// `const raw_ref_experimental`.
-#endif  // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
-
-// Verifies that `base::GetRawReference()` returns a T&.
-TEST(RawRef, GetRawReference) {
-  int x = 123;
-  raw_ref_experimental<int> miracle_ref(x);
-  static_assert(
-      std::is_same_v<decltype(base::GetRawReference(miracle_ref)), int&>);
-}
-
 #if BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
 
 TEST(AsanBackupRefPtrImpl, RawRefGet) {
diff --git a/base/allocator/partition_allocator/shim/allocator_interception_mac.mm b/base/allocator/partition_allocator/shim/allocator_interception_mac.mm
index 1db2f0cb..143f015 100644
--- a/base/allocator/partition_allocator/shim/allocator_interception_mac.mm
+++ b/base/allocator/partition_allocator/shim/allocator_interception_mac.mm
@@ -132,8 +132,9 @@
 
 void* oom_killer_malloc(struct _malloc_zone_t* zone, size_t size) {
   void* result = g_old_zone.malloc(zone, size);
-  if (!result && size)
+  if (!result && size) {
     partition_alloc::TerminateBecauseOutOfMemory(size);
+  }
   return result;
 }
 
@@ -141,15 +142,17 @@
                         size_t num_items,
                         size_t size) {
   void* result = g_old_zone.calloc(zone, num_items, size);
-  if (!result && num_items && size)
+  if (!result && num_items && size) {
     partition_alloc::TerminateBecauseOutOfMemory(num_items * size);
+  }
   return result;
 }
 
 void* oom_killer_valloc(struct _malloc_zone_t* zone, size_t size) {
   void* result = g_old_zone.valloc(zone, size);
-  if (!result && size)
+  if (!result && size) {
     partition_alloc::TerminateBecauseOutOfMemory(size);
+  }
   return result;
 }
 
@@ -159,8 +162,9 @@
 
 void* oom_killer_realloc(struct _malloc_zone_t* zone, void* ptr, size_t size) {
   void* result = g_old_zone.realloc(zone, ptr, size);
-  if (!result && size)
+  if (!result && size) {
     partition_alloc::TerminateBecauseOutOfMemory(size);
+  }
   return result;
 }
 
@@ -182,8 +186,9 @@
 
 void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone, size_t size) {
   void* result = g_old_purgeable_zone.malloc(zone, size);
-  if (!result && size)
+  if (!result && size) {
     partition_alloc::TerminateBecauseOutOfMemory(size);
+  }
   return result;
 }
 
@@ -191,15 +196,17 @@
                                   size_t num_items,
                                   size_t size) {
   void* result = g_old_purgeable_zone.calloc(zone, num_items, size);
-  if (!result && num_items && size)
+  if (!result && num_items && size) {
     partition_alloc::TerminateBecauseOutOfMemory(num_items * size);
+  }
   return result;
 }
 
 void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone, size_t size) {
   void* result = g_old_purgeable_zone.valloc(zone, size);
-  if (!result && size)
+  if (!result && size) {
     partition_alloc::TerminateBecauseOutOfMemory(size);
+  }
   return result;
 }
 
@@ -211,8 +218,9 @@
                                    void* ptr,
                                    size_t size) {
   void* result = g_old_purgeable_zone.realloc(zone, ptr, size);
-  if (!result && size)
+  if (!result && size) {
     partition_alloc::TerminateBecauseOutOfMemory(size);
+  }
   return result;
 }
 
@@ -258,9 +266,10 @@
                                             CFOptionFlags hint,
                                             void* info) {
   void* result = g_old_cfallocator_system_default(alloc_size, hint, info);
-  if (!result)
+  if (!result) {
     partition_alloc::TerminateBecauseOutOfMemory(
         static_cast<size_t>(alloc_size));
+  }
   return result;
 }
 
@@ -268,9 +277,10 @@
                                     CFOptionFlags hint,
                                     void* info) {
   void* result = g_old_cfallocator_malloc(alloc_size, hint, info);
-  if (!result)
+  if (!result) {
     partition_alloc::TerminateBecauseOutOfMemory(
         static_cast<size_t>(alloc_size));
+  }
   return result;
 }
 
@@ -278,9 +288,10 @@
                                          CFOptionFlags hint,
                                          void* info) {
   void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info);
-  if (!result)
+  if (!result) {
     partition_alloc::TerminateBecauseOutOfMemory(
         static_cast<size_t>(alloc_size));
+  }
   return result;
 }
 
@@ -293,15 +304,17 @@
 
 id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) {
   id result = g_old_allocWithZone(self, _cmd, zone);
-  if (!result)
+  if (!result) {
     partition_alloc::TerminateBecauseOutOfMemory(0);
+  }
   return result;
 }
 
 void UninterceptMallocZoneForTesting(struct _malloc_zone_t* zone) {
   ChromeMallocZone* chrome_zone = reinterpret_cast<ChromeMallocZone*>(zone);
-  if (!IsMallocZoneAlreadyStored(chrome_zone))
+  if (!IsMallocZoneAlreadyStored(chrome_zone)) {
     return;
+  }
   MallocZoneFunctions& functions = GetFunctionsForZone(zone);
   ReplaceZoneFunctions(chrome_zone, &functions);
 }
@@ -354,8 +367,9 @@
   vm_address_t* zones;
   unsigned int count;
   kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
-  if (kr != KERN_SUCCESS)
+  if (kr != KERN_SUCCESS) {
     return;
+  }
   for (unsigned int i = 0; i < count; ++i) {
     ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]);
     StoreMallocZone(zone);
@@ -374,8 +388,9 @@
   unsigned int count;
   kern_return_t kr =
       malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count);
-  if (kr != KERN_SUCCESS)
+  if (kr != KERN_SUCCESS) {
     return;
+  }
   for (unsigned int i = 0; i < count; ++i) {
     ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]);
     if (DoesMallocZoneNeedReplacing(zone, functions)) {
@@ -386,8 +401,9 @@
 }
 
 void InterceptAllocationsMac() {
-  if (g_oom_killer_enabled)
+  if (g_oom_killer_enabled) {
     return;
+  }
 
   g_oom_killer_enabled = true;
 
@@ -539,8 +555,9 @@
                                      base::TimeDelta delay) {
   ShimNewMallocZones();
 
-  if (base::Time::Now() > end_time)
+  if (base::Time::Now() > end_time) {
     return;
+  }
 
   base::TimeDelta next_delay = delay * 2;
   base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
@@ -591,12 +608,15 @@
   zone->valloc = functions->valloc;
   zone->free = functions->free;
   zone->realloc = functions->realloc;
-  if (functions->batch_malloc)
+  if (functions->batch_malloc) {
     zone->batch_malloc = functions->batch_malloc;
-  if (functions->batch_free)
+  }
+  if (functions->batch_free) {
     zone->batch_free = functions->batch_free;
-  if (functions->size)
+  }
+  if (functions->size) {
     zone->size = functions->size;
+  }
   if (zone->version >= 5 && functions->memalign) {
     zone->memalign = functions->memalign;
   }
diff --git a/base/allocator/partition_allocator/shim/allocator_interception_mac_unittest.mm b/base/allocator/partition_allocator/shim/allocator_interception_mac_unittest.mm
index 45a1b44..7d868d79 100644
--- a/base/allocator/partition_allocator/shim/allocator_interception_mac_unittest.mm
+++ b/base/allocator/partition_allocator/shim/allocator_interception_mac_unittest.mm
@@ -26,8 +26,9 @@
   vm_address_t* zones;
   unsigned int count;
   kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
-  if (kr != KERN_SUCCESS)
+  if (kr != KERN_SUCCESS) {
     return;
+  }
   for (unsigned int i = 0; i < count; ++i) {
     ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]);
     ResetMallocZone(zone);
diff --git a/base/allocator/partition_allocator/shim/allocator_shim.cc b/base/allocator/partition_allocator/shim/allocator_shim.cc
index d0e23fd5a..232e83e 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim.cc
+++ b/base/allocator/partition_allocator/shim/allocator_shim.cc
@@ -48,8 +48,9 @@
 
 ALWAYS_INLINE size_t GetCachedPageSize() {
   static size_t pagesize = 0;
-  if (!pagesize)
+  if (!pagesize) {
     pagesize = base::GetPageSize();
+  }
   return pagesize;
 }
 
@@ -60,8 +61,9 @@
   return allocator_shim::WinCallNewHandler(size);
 #else
   std::new_handler nh = std::get_new_handler();
-  if (!nh)
+  if (!nh) {
     return false;
+  }
   (*nh)();
   // Assume the new_handler will abort if it fails. Exception are disabled and
   // we don't support the case of a new_handler throwing std::bad_balloc.
diff --git a/base/allocator/partition_allocator/shim/allocator_shim.h b/base/allocator/partition_allocator/shim/allocator_shim.h
index 6d946c7f..9daa571 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim.h
+++ b/base/allocator/partition_allocator/shim/allocator_shim.h
@@ -186,7 +186,6 @@
 using SplitMainPartition = base::StrongAlias<class SplitMainPartitionTag, bool>;
 using UseDedicatedAlignedPartition =
     base::StrongAlias<class UseDedicatedAlignedPartitionTag, bool>;
-using AddDummyRefCount = base::StrongAlias<class AddDummyRefCountTag, bool>;
 using AlternateBucketDistribution =
     base::features::AlternateBucketDistributionMode;
 
@@ -199,7 +198,6 @@
     EnableBrpPartitionMemoryReclaimer enable_brp_memory_reclaimer,
     SplitMainPartition split_main_partition,
     UseDedicatedAlignedPartition use_dedicated_aligned_partition,
-    AddDummyRefCount add_dummy_ref_count,
     AlternateBucketDistribution use_alternate_bucket_distribution);
 
 #if BUILDFLAG(USE_STARSCAN)
diff --git a/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc b/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc
index 2c833f43..d38d03d 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc
+++ b/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc
@@ -36,8 +36,9 @@
 void* GlibcMalloc(const AllocatorDispatch*, size_t size, void* context) {
   // Cannot force glibc's malloc() to crash when a large size is requested, do
   // it in the shim instead.
-  if (PA_UNLIKELY(size >= kMaxAllowedSize))
+  if (PA_UNLIKELY(size >= kMaxAllowedSize)) {
     base::TerminateBecauseOutOfMemory(size);
+  }
 
   return __libc_malloc(size);
 }
@@ -45,8 +46,9 @@
 void* GlibcUncheckedMalloc(const AllocatorDispatch*,
                            size_t size,
                            void* context) {
-  if (PA_UNLIKELY(size >= kMaxAllowedSize))
+  if (PA_UNLIKELY(size >= kMaxAllowedSize)) {
     return nullptr;
+  }
 
   return __libc_malloc(size);
 }
@@ -56,8 +58,9 @@
                   size_t size,
                   void* context) {
   const auto total = partition_alloc::internal::base::CheckMul(n, size);
-  if (PA_UNLIKELY(!total.IsValid() || total.ValueOrDie() >= kMaxAllowedSize))
+  if (PA_UNLIKELY(!total.IsValid() || total.ValueOrDie() >= kMaxAllowedSize)) {
     base::TerminateBecauseOutOfMemory(size * n);
+  }
 
   return __libc_calloc(n, size);
 }
@@ -66,8 +69,9 @@
                    void* address,
                    size_t size,
                    void* context) {
-  if (PA_UNLIKELY(size >= kMaxAllowedSize))
+  if (PA_UNLIKELY(size >= kMaxAllowedSize)) {
     base::TerminateBecauseOutOfMemory(size);
+  }
 
   return __libc_realloc(address, size);
 }
@@ -76,8 +80,9 @@
                     size_t alignment,
                     size_t size,
                     void* context) {
-  if (PA_UNLIKELY(size >= kMaxAllowedSize))
+  if (PA_UNLIKELY(size >= kMaxAllowedSize)) {
     base::TerminateBecauseOutOfMemory(size);
+  }
 
   return __libc_memalign(alignment, size);
 }
diff --git a/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_partition_alloc.cc b/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_partition_alloc.cc
index 4f7a87d..300c161 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_partition_alloc.cc
+++ b/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_partition_alloc.cc
@@ -74,8 +74,9 @@
 
   PA_ALWAYS_INLINE T* Get() {
     auto* instance = instance_.load(std::memory_order_acquire);
-    if (PA_LIKELY(instance))
+    if (PA_LIKELY(instance)) {
       return instance;
+    }
 
     return GetSlowPath();
   }
@@ -130,8 +131,9 @@
 
   T* instance = instance_.load(std::memory_order_relaxed);
   // Someone beat us.
-  if (instance)
+  if (instance) {
     return instance;
+  }
 
   instance = Constructor::New(reinterpret_cast<void*>(instance_buffer_));
   instance_.store(instance, std::memory_order_release);
@@ -250,10 +252,11 @@
   // Apple only since it's not needed elsewhere, and there is a performance
   // penalty.
 
-  if (value)
+  if (value) {
     g_alloc_flags = 0;
-  else
+  } else {
     g_alloc_flags = partition_alloc::AllocFlags::kReturnNull;
+  }
 #endif
 }
 
@@ -318,12 +321,14 @@
     new_ptr = AllocateAlignedMemory(alignment, size);
   } else {
     // size == 0 and address != null means just "free(address)".
-    if (address)
+    if (address) {
       partition_alloc::ThreadSafePartitionRoot::FreeNoHooks(address);
+    }
   }
   // The original memory block (specified by address) is unchanged if ENOMEM.
-  if (!new_ptr)
+  if (!new_ptr) {
     return nullptr;
+  }
   // TODO(tasak): Need to compare the new alignment with the address' alignment.
   // If the two alignments are not the same, need to return nullptr with EINVAL.
   if (address) {
@@ -418,8 +423,9 @@
                                 void* context) {
   // This is used to implement malloc_usable_size(3). Per its man page, "if ptr
   // is NULL, 0 is returned".
-  if (!address)
+  if (!address) {
     return 0;
+  }
 
 #if BUILDFLAG(IS_APPLE)
   if (!partition_alloc::IsManagedByPartitionAlloc(
@@ -528,9 +534,10 @@
   ::partition_alloc::MemoryReclaimer::Instance()->RegisterPartition(
       Allocator());
   auto* original_root = OriginalAllocator();
-  if (original_root)
+  if (original_root) {
     ::partition_alloc::MemoryReclaimer::Instance()->RegisterPartition(
         original_root);
+  }
   if (AlignedAllocator() != Allocator()) {
     ::partition_alloc::MemoryReclaimer::Instance()->RegisterPartition(
         AlignedAllocator());
@@ -543,7 +550,6 @@
     EnableBrpPartitionMemoryReclaimer enable_brp_memory_reclaimer,
     SplitMainPartition split_main_partition,
     UseDedicatedAlignedPartition use_dedicated_aligned_partition,
-    AddDummyRefCount add_dummy_ref_count,
     AlternateBucketDistribution use_alternate_bucket_distribution) {
   // BRP cannot be enabled without splitting the main partition. Furthermore, in
   // the "before allocation" mode, it can't be enabled without further splitting
@@ -603,11 +609,7 @@
               ? partition_alloc::PartitionOptions::BackupRefPtrZapping::kEnabled
               : partition_alloc::PartitionOptions::BackupRefPtrZapping::
                     kDisabled,
-          partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
-          add_dummy_ref_count
-              ? partition_alloc::PartitionOptions::AddDummyRefCount::kEnabled
-              : partition_alloc::PartitionOptions::AddDummyRefCount::
-                    kDisabled));
+          partition_alloc::PartitionOptions::UseConfigurablePool::kNo));
   partition_alloc::ThreadSafePartitionRoot* new_root = new_main_partition.get();
 
   partition_alloc::ThreadSafePartitionRoot* new_aligned_root;
@@ -676,12 +678,14 @@
   partition_alloc::internal::PCScan::Initialize(config);
 
   partition_alloc::internal::PCScan::RegisterScannableRoot(Allocator());
-  if (OriginalAllocator() != nullptr)
+  if (OriginalAllocator() != nullptr) {
     partition_alloc::internal::PCScan::RegisterScannableRoot(
         OriginalAllocator());
-  if (Allocator() != AlignedAllocator())
+  }
+  if (Allocator() != AlignedAllocator()) {
     partition_alloc::internal::PCScan::RegisterScannableRoot(
         AlignedAllocator());
+  }
 
   base::internal::NonScannableAllocator::Instance().NotifyPCScanEnabled();
   base::internal::NonQuarantinableAllocator::Instance().NotifyPCScanEnabled();
@@ -760,15 +764,17 @@
   auto& nonscannable_allocator =
       base::internal::NonScannableAllocator::Instance();
   partition_alloc::SimplePartitionStatsDumper nonscannable_allocator_dumper;
-  if (auto* nonscannable_root = nonscannable_allocator.root())
+  if (auto* nonscannable_root = nonscannable_allocator.root()) {
     nonscannable_root->DumpStats("malloc", true,
                                  &nonscannable_allocator_dumper);
+  }
   auto& nonquarantinable_allocator =
       base::internal::NonQuarantinableAllocator::Instance();
   partition_alloc::SimplePartitionStatsDumper nonquarantinable_allocator_dumper;
-  if (auto* nonquarantinable_root = nonquarantinable_allocator.root())
+  if (auto* nonquarantinable_root = nonquarantinable_allocator.root()) {
     nonquarantinable_root->DumpStats("malloc", true,
                                      &nonquarantinable_allocator_dumper);
+  }
 
   struct mallinfo info = {0};
   info.arena = 0;  // Memory *not* allocated with mmap().
diff --git a/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_winheap.cc b/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_winheap.cc
index adc15be..23d498e 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_winheap.cc
+++ b/base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_winheap.cc
@@ -25,8 +25,9 @@
                                void* context) {
   // Overflow check.
   const size_t size = n * elem_size;
-  if (elem_size != 0 && size / elem_size != n)
+  if (elem_size != 0 && size / elem_size != n) {
     return nullptr;
+  }
 
   void* result = DefaultWinHeapMallocImpl(self, size, context);
   if (result) {
diff --git a/base/allocator/partition_allocator/shim/allocator_shim_override_linker_wrapped_symbols.h b/base/allocator/partition_allocator/shim/allocator_shim_override_linker_wrapped_symbols.h
index 6218731..08c0536 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim_override_linker_wrapped_symbols.h
+++ b/base/allocator/partition_allocator/shim/allocator_shim_override_linker_wrapped_symbols.h
@@ -71,12 +71,14 @@
 
 SHIM_ALWAYS_EXPORT char* __wrap_realpath(const char* path,
                                          char* resolved_path) {
-  if (resolved_path)
+  if (resolved_path) {
     return __real_realpath(path, resolved_path);
+  }
 
   char buffer[kPathMaxSize];
-  if (!__real_realpath(path, buffer))
+  if (!__real_realpath(path, buffer)) {
     return nullptr;
+  }
   return __wrap_strdup(buffer);
 }
 
@@ -85,16 +87,18 @@
 SHIM_ALWAYS_EXPORT char* __wrap_strdup(const char* str) {
   std::size_t length = std::strlen(str) + 1;
   void* buffer = ShimMalloc(length, nullptr);
-  if (!buffer)
+  if (!buffer) {
     return nullptr;
+  }
   return reinterpret_cast<char*>(std::memcpy(buffer, str, length));
 }
 
 SHIM_ALWAYS_EXPORT char* __wrap_strndup(const char* str, size_t n) {
   std::size_t length = std::min(std::strlen(str), n);
   char* buffer = reinterpret_cast<char*>(ShimMalloc(length + 1, nullptr));
-  if (!buffer)
+  if (!buffer) {
     return nullptr;
+  }
   std::memcpy(buffer, str, length);
   buffer[length] = '\0';
   return buffer;
@@ -105,14 +109,17 @@
 extern char* __real_getcwd(char* buffer, size_t size);
 
 SHIM_ALWAYS_EXPORT char* __wrap_getcwd(char* buffer, size_t size) {
-  if (buffer)
+  if (buffer) {
     return __real_getcwd(buffer, size);
+  }
 
-  if (!size)
+  if (!size) {
     size = kPathMaxSize;
+  }
   char local_buffer[size];
-  if (!__real_getcwd(local_buffer, size))
+  if (!__real_getcwd(local_buffer, size)) {
     return nullptr;
+  }
   return __wrap_strdup(local_buffer);
 }
 
@@ -128,8 +135,9 @@
       malloc(kInitialSize));  // Our malloc() doesn't return nullptr.
 
   int actual_size = vsnprintf(*strp, kInitialSize, fmt, va_args);
-  if (actual_size < 0)
+  if (actual_size < 0) {
     return actual_size;
+  }
   *strp =
       static_cast<char*>(realloc(*strp, static_cast<size_t>(actual_size + 1)));
 
@@ -139,8 +147,9 @@
   //
   // This is very lightly used in Chromium in practice, see crbug.com/116558 for
   // details.
-  if (actual_size >= kInitialSize)
+  if (actual_size >= kInitialSize) {
     return vsnprintf(*strp, static_cast<size_t>(actual_size + 1), fmt, va_args);
+  }
 
   return actual_size;
 }
diff --git a/base/allocator/partition_allocator/shim/allocator_shim_override_mac_default_zone.h b/base/allocator/partition_allocator/shim/allocator_shim_override_mac_default_zone.h
index f096971d..5d7b6c5 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim_override_mac_default_zone.h
+++ b/base/allocator/partition_allocator/shim/allocator_shim_override_mac_default_zone.h
@@ -347,8 +347,9 @@
 // receives an address allocated by the system allocator.
 __attribute__((constructor(0))) void
 InitializeDefaultMallocZoneWithPartitionAlloc() {
-  if (IsAlreadyRegistered())
+  if (IsAlreadyRegistered()) {
     return;
+  }
 
   // Instantiate the existing regular and purgeable zones in order to make the
   // existing purgeable zone use the existing regular zone since PartitionAlloc
diff --git a/base/allocator/partition_allocator/shim/allocator_shim_unittest.cc b/base/allocator/partition_allocator/shim/allocator_shim_unittest.cc
index 626d098..7c062c8 100644
--- a/base/allocator/partition_allocator/shim/allocator_shim_unittest.cc
+++ b/base/allocator/partition_allocator/shim/allocator_shim_unittest.cc
@@ -65,16 +65,18 @@
   static void* MockAlloc(const AllocatorDispatch* self,
                          size_t size,
                          void* context) {
-    if (instance_ && size < MaxSizeTracked())
+    if (instance_ && size < MaxSizeTracked()) {
       ++(instance_->allocs_intercepted_by_size[size]);
+    }
     return self->next->alloc_function(self->next, size, context);
   }
 
   static void* MockAllocUnchecked(const AllocatorDispatch* self,
                                   size_t size,
                                   void* context) {
-    if (instance_ && size < MaxSizeTracked())
+    if (instance_ && size < MaxSizeTracked()) {
       ++(instance_->allocs_intercepted_by_size[size]);
+    }
     return self->next->alloc_unchecked_function(self->next, size, context);
   }
 
@@ -83,8 +85,9 @@
                                  size_t size,
                                  void* context) {
     const size_t real_size = n * size;
-    if (instance_ && real_size < MaxSizeTracked())
+    if (instance_ && real_size < MaxSizeTracked()) {
       ++(instance_->zero_allocs_intercepted_by_size[real_size]);
+    }
     return self->next->alloc_zero_initialized_function(self->next, n, size,
                                                        context);
   }
@@ -94,10 +97,12 @@
                                 size_t size,
                                 void* context) {
     if (instance_) {
-      if (size < MaxSizeTracked())
+      if (size < MaxSizeTracked()) {
         ++(instance_->aligned_allocs_intercepted_by_size[size]);
-      if (alignment < MaxSizeTracked())
+      }
+      if (alignment < MaxSizeTracked()) {
         ++(instance_->aligned_allocs_intercepted_by_alignment[alignment]);
+      }
     }
     return self->next->alloc_aligned_function(self->next, alignment, size,
                                               context);
@@ -120,8 +125,9 @@
         return address;
       }
 
-      if (size < MaxSizeTracked())
+      if (size < MaxSizeTracked()) {
         ++(instance_->reallocs_intercepted_by_size[size]);
+      }
       ++instance_->reallocs_intercepted_by_addr[Hash(address)];
     }
     return self->next->realloc_function(self->next, address, size, context);
@@ -140,8 +146,9 @@
                                     void* address,
                                     void* context) {
     // Special testing values for GetSizeEstimate() interception.
-    if (address == kTestSizeEstimateAddress)
+    if (address == kTestSizeEstimateAddress) {
       return kTestSizeEstimate;
+    }
     return self->next->get_size_estimate_function(self->next, address, context);
   }
 
@@ -149,8 +156,9 @@
                                  void* address,
                                  void* context) {
     // The same as MockGetSizeEstimate.
-    if (address == kTestSizeEstimateAddress)
+    if (address == kTestSizeEstimateAddress) {
       return true;
+    }
     return self->next->claimed_address_function(self->next, address, context);
   }
 
@@ -217,8 +225,9 @@
                                   size_t alignment,
                                   void* context) {
     if (instance_) {
-      if (size < MaxSizeTracked())
+      if (size < MaxSizeTracked()) {
         ++instance_->aligned_reallocs_intercepted_by_size[size];
+      }
       ++instance_->aligned_reallocs_intercepted_by_addr[Hash(address)];
     }
     return self->next->aligned_realloc_function(self->next, address, size,
@@ -235,8 +244,9 @@
   }
 
   static void NewHandler() {
-    if (!instance_)
+    if (!instance_) {
       return;
+    }
     instance_->num_new_handler_calls.fetch_add(1, std::memory_order_relaxed);
   }
 
@@ -603,15 +613,17 @@
 
   ThreadDelegateForNewHandlerTest mock_thread_main(&event);
 
-  for (auto& thread : threads)
+  for (auto& thread : threads) {
     base::PlatformThread::Create(0, &mock_thread_main, &thread);
+  }
 
   std::set_new_handler(&AllocatorShimTest::NewHandler);
   SetCallNewHandlerOnMallocFailure(true);  // It's going to fail on realloc().
   InsertAllocatorDispatch(&g_mock_dispatch);
   event.Signal();
-  for (auto& thread : threads)
+  for (auto& thread : threads) {
     base::PlatformThread::Join(thread);
+  }
   RemoveAllocatorDispatchForTesting(&g_mock_dispatch);
   ASSERT_EQ(kNumThreads, GetNumberOfNewHandlerCalls());
 }
@@ -662,8 +674,9 @@
 TEST_F(AllocatorShimTest, InterceptCLibraryFunctions) {
   auto total_counts = [](const std::vector<size_t>& counts) {
     size_t total = 0;
-    for (const auto count : counts)
+    for (const auto count : counts) {
       total += count;
+    }
     return total;
   };
   size_t counts_before;
diff --git a/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.cc b/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.cc
index 5c69325..5315ea28 100644
--- a/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.cc
+++ b/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.cc
@@ -72,8 +72,9 @@
   EnsureMallocZonesInitializedLocked();
   GetLock().AssertAcquired();
   for (int i = 0; i < g_zone_count; ++i) {
-    if (g_malloc_zones[i].context == reinterpret_cast<void*>(zone))
+    if (g_malloc_zones[i].context == reinterpret_cast<void*>(zone)) {
       return true;
+    }
   }
   return false;
 }
@@ -83,11 +84,13 @@
 bool StoreMallocZone(ChromeMallocZone* zone) {
   base::AutoLock l(GetLock());
   EnsureMallocZonesInitializedLocked();
-  if (IsMallocZoneAlreadyStoredLocked(zone))
+  if (IsMallocZoneAlreadyStoredLocked(zone)) {
     return false;
+  }
 
-  if (g_zone_count == kMaxZoneCount)
+  if (g_zone_count == kMaxZoneCount) {
     return false;
+  }
 
   StoreZoneFunctions(zone, &g_malloc_zones[g_zone_count]);
   ++g_zone_count;
diff --git a/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.h b/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.h
index 984f3d5..74a45ae 100644
--- a/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.h
+++ b/base/allocator/partition_allocator/shim/malloc_zone_functions_mac.h
@@ -95,8 +95,9 @@
 
 inline MallocZoneFunctions& GetFunctionsForZone(void* zone) {
   for (unsigned int i = 0; i < kMaxZoneCount; ++i) {
-    if (g_malloc_zones[i].context == zone)
+    if (g_malloc_zones[i].context == zone) {
       return g_malloc_zones[i];
+    }
   }
   PA_IMMEDIATE_CRASH();
 }
diff --git a/base/allocator/partition_allocator/shim/winheap_stubs_win.cc b/base/allocator/partition_allocator/shim/winheap_stubs_win.cc
index eb8a456..b8cccdc9 100644
--- a/base/allocator/partition_allocator/shim/winheap_stubs_win.cc
+++ b/base/allocator/partition_allocator/shim/winheap_stubs_win.cc
@@ -35,33 +35,38 @@
 }  // namespace
 
 void* WinHeapMalloc(size_t size) {
-  if (size < kMaxWindowsAllocation)
+  if (size < kMaxWindowsAllocation) {
     return HeapAlloc(get_heap_handle(), 0, size);
+  }
   return nullptr;
 }
 
 void WinHeapFree(void* ptr) {
-  if (!ptr)
+  if (!ptr) {
     return;
+  }
 
   HeapFree(get_heap_handle(), 0, ptr);
 }
 
 void* WinHeapRealloc(void* ptr, size_t size) {
-  if (!ptr)
+  if (!ptr) {
     return WinHeapMalloc(size);
+  }
   if (!size) {
     WinHeapFree(ptr);
     return nullptr;
   }
-  if (size < kMaxWindowsAllocation)
+  if (size < kMaxWindowsAllocation) {
     return HeapReAlloc(get_heap_handle(), 0, ptr, size);
+  }
   return nullptr;
 }
 
 size_t WinHeapGetSizeEstimate(void* ptr) {
-  if (!ptr)
+  if (!ptr) {
     return 0;
+  }
 
   return HeapSize(get_heap_handle(), 0, ptr);
 }
@@ -74,8 +79,9 @@
 #endif  // _CPPUNWIND
   // Get the current new handler.
   _PNH nh = _query_new_handler();
-  if (!nh)
+  if (!nh) {
     return false;
+  }
   // Since exceptions are disabled, we don't really know if new_handler
   // failed.  Assume it will abort if it fails.
   return nh(size) ? true : false;
@@ -152,12 +158,14 @@
   PA_CHECK(partition_alloc::internal::base::bits::IsPowerOfTwo(alignment));
 
   size_t adjusted = AdjustedSize(size, alignment);
-  if (adjusted >= kMaxWindowsAllocation)
+  if (adjusted >= kMaxWindowsAllocation) {
     return nullptr;
+  }
 
   void* ptr = WinHeapMalloc(adjusted);
-  if (!ptr)
+  if (!ptr) {
     return nullptr;
+  }
 
   return AlignAllocation(ptr, alignment);
 }
@@ -165,16 +173,18 @@
 void* WinHeapAlignedRealloc(void* ptr, size_t size, size_t alignment) {
   PA_CHECK(partition_alloc::internal::base::bits::IsPowerOfTwo(alignment));
 
-  if (!ptr)
+  if (!ptr) {
     return WinHeapAlignedMalloc(size, alignment);
+  }
   if (!size) {
     WinHeapAlignedFree(ptr);
     return nullptr;
   }
 
   size_t adjusted = AdjustedSize(size, alignment);
-  if (adjusted >= kMaxWindowsAllocation)
+  if (adjusted >= kMaxWindowsAllocation) {
     return nullptr;
+  }
 
   // Try to resize the allocation in place first.
   void* unaligned = UnalignAllocation(ptr);
@@ -187,8 +197,9 @@
   // unaligned allocation from HeapReAlloc() would force us to copy the
   // allocation twice.
   void* new_ptr = WinHeapAlignedMalloc(size, alignment);
-  if (!new_ptr)
+  if (!new_ptr) {
     return nullptr;
+  }
 
   size_t gap =
       reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(unaligned);
@@ -199,8 +210,9 @@
 }
 
 void WinHeapAlignedFree(void* ptr) {
-  if (!ptr)
+  if (!ptr) {
     return;
+  }
 
   void* original_allocation = UnalignAllocation(ptr);
   WinHeapFree(original_allocation);
diff --git a/base/allocator/partition_allocator/thread_isolation/alignment.h b/base/allocator/partition_allocator/thread_isolation/alignment.h
index 66301f63..84b2b31 100644
--- a/base/allocator/partition_allocator/thread_isolation/alignment.h
+++ b/base/allocator/partition_allocator/thread_isolation/alignment.h
@@ -24,14 +24,20 @@
 // Calculate the required padding so that the last element of a page-aligned
 // array lands on a page boundary. In other words, calculate that padding so
 // that (count-1) elements are a multiple of page size.
+// The offset parameter additionally skips bytes in the object, e.g.
+// object+offset will be page aligned.
+#define PA_THREAD_ISOLATED_ARRAY_PAD_SZ_WITH_OFFSET(Type, count, offset) \
+  PA_THREAD_ISOLATED_FILL_PAGE_SZ(sizeof(Type) * (count - 1) + offset)
+
 #define PA_THREAD_ISOLATED_ARRAY_PAD_SZ(Type, count) \
-  PA_THREAD_ISOLATED_FILL_PAGE_SZ(sizeof(Type) * (count - 1))
+  PA_THREAD_ISOLATED_ARRAY_PAD_SZ_WITH_OFFSET(Type, count, 0)
 
 #else  // BUILDFLAG(ENABLE_THREAD_ISOLATION)
 
 #define PA_THREAD_ISOLATED_ALIGN
 #define PA_THREAD_ISOLATED_FILL_PAGE_SZ(size) 0
 #define PA_THREAD_ISOLATED_ARRAY_PAD_SZ(Type, size) 0
+#define PA_THREAD_ISOLATED_ARRAY_PAD_SZ_WITH_OFFSET(Type, size, offset) 0
 
 #endif  // BUILDFLAG(ENABLE_THREAD_ISOLATION)
 
diff --git a/base/allocator/partition_allocator/thread_isolation/pkey_unittest.cc b/base/allocator/partition_allocator/thread_isolation/pkey_unittest.cc
index a4967f2..d08ed9b 100644
--- a/base/allocator/partition_allocator/thread_isolation/pkey_unittest.cc
+++ b/base/allocator/partition_allocator/thread_isolation/pkey_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/allocator/partition_allocator/address_pool_manager.h"
 #include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
 #include "base/allocator/partition_allocator/thread_isolation/thread_isolation.h"
 
@@ -11,6 +12,7 @@
 #include <sys/mman.h>
 #include <sys/syscall.h>
 
+#include "base/allocator/partition_allocator/address_space_stats.h"
 #include "base/allocator/partition_allocator/page_allocator.h"
 #include "base/allocator/partition_allocator/page_allocator_constants.h"
 #include "base/allocator/partition_allocator/partition_alloc.h"
@@ -23,6 +25,7 @@
 constexpr size_t kIsolatedThreadStackSize = 64 * 1024;
 constexpr int kNumPkey = 16;
 constexpr size_t kTestReturnValue = 0x8765432187654321llu;
+constexpr uint32_t kPKRUAllowAccessNoWrite = 0b10101010101010101010101010101000;
 
 namespace partition_alloc::internal {
 
@@ -75,7 +78,7 @@
 
 class PkeyTest : public testing::Test {
  protected:
-  void PkeyProtectMemory() {
+  static void PkeyProtectMemory() {
     PA_PCHECK(dl_iterate_phdr(ProtectROSegments, nullptr) == 0);
 
     PA_PCHECK(PkeyMprotect(&isolatedGlobals, sizeof(isolatedGlobals),
@@ -86,7 +89,7 @@
                            isolatedGlobals.pkey) == 0);
   }
 
-  void InitializeIsolatedThread() {
+  static void InitializeIsolatedThread() {
     isolatedGlobals.isolatedStack =
         mmap(nullptr, kIsolatedThreadStackSize, PROT_READ | PROT_WRITE,
              MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, -1, 0);
@@ -96,6 +99,12 @@
   }
 
   void SetUp() override {
+    // SetUp only once, but we can't do it in SetUpTestSuite since that runs
+    // before other PartitionAlloc initialization happened.
+    if (isolatedGlobals.pkey != kInvalidPkey) {
+      return;
+    }
+
     int pkey = PkeyAlloc(0);
     if (pkey == -1) {
       return;
@@ -110,15 +119,16 @@
         partition_alloc::PartitionOptions::BackupRefPtr::kDisabled,
         partition_alloc::PartitionOptions::BackupRefPtrZapping::kDisabled,
         partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
-        partition_alloc::PartitionOptions::AddDummyRefCount::kDisabled,
         partition_alloc::ThreadIsolationOption(isolatedGlobals.pkey),
     });
     isolatedGlobals.allocatorRoot = isolatedGlobals.allocator->root();
 
     InitializeIsolatedThread();
+
+    Wrpkru(kPKRUAllowAccessNoWrite);
   }
 
-  void TearDown() override {
+  static void TearDownTestSuite() {
     if (isolatedGlobals.pkey == kInvalidPkey) {
       return;
     }
@@ -249,6 +259,22 @@
   ASSERT_EQ(ret, kTestReturnValue);
 }
 
+class MockAddressSpaceStatsDumper : public AddressSpaceStatsDumper {
+ public:
+  MockAddressSpaceStatsDumper() = default;
+  void DumpStats(const AddressSpaceStats* address_space_stats) override {}
+};
+
+TEST_F(PkeyTest, DumpPkeyPoolStats) {
+  if (isolatedGlobals.pkey == kInvalidPkey) {
+    return;
+  }
+
+  MockAddressSpaceStatsDumper mock_stats_dumper;
+  partition_alloc::internal::AddressPoolManager::GetInstance().DumpStats(
+      &mock_stats_dumper);
+}
+
 }  // namespace partition_alloc::internal
 
 #endif  // BUILDFLAG(ENABLE_THREAD_ISOLATION)
diff --git a/base/allocator/partition_allocator/thread_isolation/thread_isolation.cc b/base/allocator/partition_allocator/thread_isolation/thread_isolation.cc
index 594e40c..3df0ade 100644
--- a/base/allocator/partition_allocator/thread_isolation/thread_isolation.cc
+++ b/base/allocator/partition_allocator/thread_isolation/thread_isolation.cc
@@ -38,8 +38,10 @@
 
 template <typename T>
 void WriteProtectThreadIsolatedVariable(ThreadIsolationOption thread_isolation,
-                                        T& var) {
-  WriteProtectThreadIsolatedMemory(thread_isolation, &var, sizeof(T));
+                                        T& var,
+                                        size_t offset = 0) {
+  WriteProtectThreadIsolatedMemory(thread_isolation, (char*)&var + offset,
+                                   sizeof(T) - offset);
 }
 
 int MprotectWithThreadIsolation(void* addr,
@@ -57,7 +59,7 @@
 
   AddressPoolManager::Pool* pool =
       AddressPoolManager::GetInstance().GetPool(kThreadIsolatedPoolHandle);
-  WriteProtectThreadIsolatedVariable(thread_isolation, *pool);
+  WriteProtectThreadIsolatedVariable(thread_isolation, *pool, sizeof(Lock));
 
   uint16_t* pkey_reservation_offset_table =
       GetReservationOffsetTable(kThreadIsolatedPoolHandle);
diff --git a/base/apple/DIR_METADATA b/base/apple/DIR_METADATA
new file mode 100644
index 0000000..14b5edb
--- /dev/null
+++ b/base/apple/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail {
+  component: "Internals"
+}
diff --git a/base/apple/OWNERS b/base/apple/OWNERS
new file mode 100644
index 0000000..a3fc32f
--- /dev/null
+++ b/base/apple/OWNERS
@@ -0,0 +1,2 @@
+mark@chromium.org
+thakis@chromium.org
diff --git a/base/mac/bridging.h b/base/apple/bridging.h
similarity index 95%
rename from base/mac/bridging.h
rename to base/apple/bridging.h
index 9f70ad3..fd418eba 100644
--- a/base/mac/bridging.h
+++ b/base/apple/bridging.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 BASE_MAC_BRIDGING_H_
-#define BASE_MAC_BRIDGING_H_
+#ifndef BASE_APPLE_BRIDGING_H_
+#define BASE_APPLE_BRIDGING_H_
 
 #include <CoreText/CoreText.h>
 #import <Foundation/Foundation.h>
@@ -23,7 +23,7 @@
 #endif
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "base/mac/bridging.h requires ARC."
+#error "base/apple/bridging.h requires ARC."
 #endif
 
 // These functions convert pointers of bridged CFTypes to NSTypes and
@@ -58,7 +58,7 @@
 // works for this purpose.
 
 #define CF_TO_NS_CAST_IMPL(TypeCF, TypeNS)                                  \
-  namespace base::mac {                                                     \
+  namespace base::apple {                                                   \
   inline BASE_EXPORT TypeNS* _Nullable CFToNSOwnershipCast(                 \
       TypeCF##Ref CF_CONSUMED _Nullable cf_val) {                           \
     DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val));          \
@@ -85,7 +85,7 @@
 
 #define CF_TO_NS_MUTABLE_CAST_IMPL(name)                                 \
   CF_TO_NS_CAST_IMPL(CF##name, NS##name)                                 \
-  namespace base::mac {                                                  \
+  namespace base::apple {                                                \
   inline BASE_EXPORT NSMutable##name* _Nullable CFToNSOwnershipCast(     \
       CFMutable##name##Ref CF_CONSUMED _Nullable cf_val) {               \
     DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val));     \
@@ -151,7 +151,7 @@
 Boolean _CFIsObjC(CFTypeID typeID, _Nonnull CFTypeRef obj);
 }  // extern "C"
 
-namespace base::mac {
+namespace base::apple {
 
 inline BASE_EXPORT NSFont* _Nullable CFToNSOwnershipCast(
     CTFontRef CF_CONSUMED _Nullable cf_val) {
@@ -185,14 +185,14 @@
   return cf_val;
 }
 
-}  // namespace base::mac
+}  // namespace base::apple
 
 #endif  // BUILDFLAG(IS_IOS)
 
 #undef CF_TO_NS_CAST_IMPL
 #undef CF_TO_NS_MUTABLE_CAST_IMPL
 
-namespace base::mac {
+namespace base::apple {
 
 template <typename CFT>
 id _Nullable CFToNSOwnershipCast(base::ScopedCFTypeRef<CFT>) {
@@ -203,6 +203,6 @@
   return nil;
 }
 
-}  // namespace base::mac
+}  // namespace base::apple
 
-#endif  // BASE_MAC_BRIDGING_H_
+#endif  // BASE_APPLE_BRIDGING_H_
diff --git a/base/check.cc b/base/check.cc
index a94df2f2..18964aaf 100644
--- a/base/check.cc
+++ b/base/check.cc
@@ -153,14 +153,23 @@
 
 }  // namespace
 
-CheckError CheckError::Check(const char* file,
-                             int line,
-                             const char* condition) {
-  auto* const log_message = new LogMessage(file, line, LOGGING_FATAL);
+CheckError CheckError::Check(const char* condition,
+                             const base::Location& location) {
+  auto* const log_message = new LogMessage(
+      location.file_name(), location.line_number(), LOGGING_FATAL);
   log_message->stream() << "Check failed: " << condition << ". ";
   return CheckError(log_message);
 }
 
+CheckError CheckError::CheckOp(char* log_message_str,
+                               const base::Location& location) {
+  auto* const log_message = new LogMessage(
+      location.file_name(), location.line_number(), LOGGING_FATAL);
+  log_message->stream() << log_message_str;
+  free(log_message_str);
+  return CheckError(log_message);
+}
+
 CheckError CheckError::DCheck(const char* condition,
                               const base::Location& location) {
   auto* const log_message = new DCheckLogMessage(location, LOGGING_DCHECK);
@@ -168,6 +177,15 @@
   return CheckError(log_message);
 }
 
+CheckError CheckError::DCheckOp(char* log_message_str,
+                                const base::Location& location) {
+  auto* const log_message = new DCheckLogMessage(
+      location.file_name(), location.line_number(), LOGGING_FATAL);
+  log_message->stream() << log_message_str;
+  free(log_message_str);
+  return CheckError(log_message);
+}
+
 CheckError CheckError::DumpWillBeCheck(const char* condition,
                                        const base::Location& location) {
   auto* const log_message = new DumpWillBeCheckLogMessage(
@@ -176,23 +194,22 @@
   return CheckError(log_message);
 }
 
-CheckError CheckError::PCheck(const char* file,
-                              int line,
-                              const char* condition) {
+CheckError CheckError::PCheck(const char* condition,
+                              const base::Location& location) {
   SystemErrorCode err_code = logging::GetLastSystemErrorCode();
 #if BUILDFLAG(IS_WIN)
-  auto* const log_message =
-      new Win32ErrorLogMessage(file, line, LOGGING_FATAL, err_code);
+  auto* const log_message = new Win32ErrorLogMessage(
+      location.file_name(), location.line_number(), LOGGING_FATAL, err_code);
 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
-  auto* const log_message =
-      new ErrnoLogMessage(file, line, LOGGING_FATAL, err_code);
+  auto* const log_message = new ErrnoLogMessage(
+      location.file_name(), location.line_number(), LOGGING_FATAL, err_code);
 #endif
   log_message->stream() << "Check failed: " << condition << ". ";
   return CheckError(log_message);
 }
 
-CheckError CheckError::PCheck(const char* file, int line) {
-  return PCheck(file, line, "");
+CheckError CheckError::PCheck(const base::Location& location) {
+  return PCheck("", location);
 }
 
 CheckError CheckError::DPCheck(const char* condition,
@@ -209,10 +226,10 @@
   return CheckError(log_message);
 }
 
-CheckError CheckError::NotImplemented(const char* file,
-                                      int line,
-                                      const char* function) {
-  auto* const log_message = new LogMessage(file, line, LOGGING_ERROR);
+CheckError CheckError::NotImplemented(const char* function,
+                                      const base::Location& location) {
+  auto* const log_message = new LogMessage(
+      location.file_name(), location.line_number(), LOGGING_ERROR);
   log_message->stream() << "Not implemented reached in " << function;
   return CheckError(log_message);
 }
@@ -253,14 +270,16 @@
 void NotReachedError::TriggerNotReached() {
   // This triggers a NOTREACHED() error as the returned NotReachedError goes out
   // of scope.
-  NotReached();
+  NotReached()
+      << "NOTREACHED log messages are omitted in official builds. Sorry!";
 }
 
 NotReachedError::~NotReachedError() = default;
 
-NotReachedNoreturnError::NotReachedNoreturnError(const char* file, int line)
-    : CheckError([file, line]() {
-        auto* const log_message = new LogMessage(file, line, LOGGING_FATAL);
+NotReachedNoreturnError::NotReachedNoreturnError(const base::Location& location)
+    : CheckError([location]() {
+        auto* const log_message = new LogMessage(
+            location.file_name(), location.line_number(), LOGGING_FATAL);
         log_message->stream() << "NOTREACHED hit. ";
         return log_message;
       }()) {}
@@ -277,22 +296,6 @@
   base::ImmediateCrash();
 }
 
-LogMessage* CheckOpResult::CreateLogMessage(bool is_dcheck,
-                                            const char* file,
-                                            int line,
-                                            const char* expr_str,
-                                            char* v1_str,
-                                            char* v2_str) {
-  LogMessage* const log_message =
-      is_dcheck ? new DCheckLogMessage(file, line, LOGGING_DCHECK)
-                : new LogMessage(file, line, LOGGING_FATAL);
-  log_message->stream() << "Check failed: " << expr_str << " (" << v1_str
-                        << " vs. " << v2_str << ")";
-  free(v1_str);
-  free(v2_str);
-  return log_message;
-}
-
 void RawCheckFailure(const char* message) {
   RawLog(LOGGING_FATAL, message);
   __builtin_unreachable();
diff --git a/base/check.h b/base/check.h
index b988149..4bf8146 100644
--- a/base/check.h
+++ b/base/check.h
@@ -63,29 +63,41 @@
 // Class used for raising a check error upon destruction.
 class BASE_EXPORT CheckError {
  public:
-  // Used by CheckOp. Takes ownership of `log_message`.
-  explicit CheckError(LogMessage* log_message) : log_message_(log_message) {}
-
-  static CheckError Check(const char* file, int line, const char* condition);
+  static CheckError Check(
+      const char* condition,
+      const base::Location& location = base::Location::Current());
+  // Takes ownership over (free()s after using) `log_message_str`, for use with
+  // CHECK_op macros.
+  static CheckError CheckOp(
+      char* log_message_str,
+      const base::Location& location = base::Location::Current());
 
   static CheckError DCheck(
       const char* condition,
       const base::Location& location = base::Location::Current());
+  // Takes ownership over (free()s after using) `log_message_str`, for use with
+  // DCHECK_op macros.
+  static CheckError DCheckOp(
+      char* log_message_str,
+      const base::Location& location = base::Location::Current());
 
   static CheckError DumpWillBeCheck(
       const char* condition,
       const base::Location& location = base::Location::Current());
 
-  static CheckError PCheck(const char* file, int line, const char* condition);
-  static CheckError PCheck(const char* file, int line);
+  static CheckError PCheck(
+      const char* condition,
+      const base::Location& location = base::Location::Current());
+  static CheckError PCheck(
+      const base::Location& location = base::Location::Current());
 
   static CheckError DPCheck(
       const char* condition,
       const base::Location& location = base::Location::Current());
 
-  static CheckError NotImplemented(const char* file,
-                                   int line,
-                                   const char* function);
+  static CheckError NotImplemented(
+      const char* function,
+      const base::Location& location = base::Location::Current());
 
   // Stream for adding optional details to the error message.
   std::ostream& stream();
@@ -103,6 +115,9 @@
   }
 
  protected:
+  // Takes ownership of `log_message`.
+  explicit CheckError(LogMessage* log_message) : log_message_(log_message) {}
+
   LogMessage* const log_message_;
 };
 
@@ -127,7 +142,8 @@
 // callers of NOTREACHED() have migrated to the CHECK-fatal version.
 class BASE_EXPORT NotReachedNoreturnError : public CheckError {
  public:
-  NotReachedNoreturnError(const char* file, int line);
+  explicit NotReachedNoreturnError(
+      const base::Location& location = base::Location::Current());
 
   [[noreturn]] NOMERGE NOINLINE NOT_TAIL_CALLED ~NotReachedNoreturnError();
 };
@@ -171,22 +187,18 @@
 #define CHECK_WILL_STREAM() false
 
 // Strip the conditional string from official builds.
-#define PCHECK(condition)                                                \
-  CHECK_FUNCTION_IMPL(::logging::CheckError::PCheck(__FILE__, __LINE__), \
-                      condition)
+#define PCHECK(condition) \
+  CHECK_FUNCTION_IMPL(::logging::CheckError::PCheck(), condition)
 
 #else
 
 #define CHECK_WILL_STREAM() true
 
 #define CHECK(condition) \
-  CHECK_FUNCTION_IMPL(   \
-      ::logging::CheckError::Check(__FILE__, __LINE__, #condition), condition)
+  CHECK_FUNCTION_IMPL(::logging::CheckError::Check(#condition), condition)
 
-#define PCHECK(condition)                                            \
-  CHECK_FUNCTION_IMPL(                                               \
-      ::logging::CheckError::PCheck(__FILE__, __LINE__, #condition), \
-      condition)
+#define PCHECK(condition) \
+  CHECK_FUNCTION_IMPL(::logging::CheckError::PCheck(#condition), condition)
 
 #endif
 
diff --git a/base/check_op.cc b/base/check_op.cc
index 120824d4..5c0b3318 100644
--- a/base/check_op.cc
+++ b/base/check_op.cc
@@ -76,4 +76,15 @@
   return strdup(ss.str().c_str());
 }
 
+char* CreateCheckOpLogMessageString(const char* expr_str,
+                                    char* v1_str,
+                                    char* v2_str) {
+  std::stringstream ss;
+  ss << "Check failed: " << expr_str << " (" << v1_str << " vs. " << v2_str
+     << ")";
+  free(v1_str);
+  free(v2_str);
+  return strdup(ss.str().c_str());
+}
+
 }  // namespace logging
diff --git a/base/check_op.h b/base/check_op.h
index acaf45f2..71d29c4e 100644
--- a/base/check_op.h
+++ b/base/check_op.h
@@ -122,53 +122,31 @@
       static_cast<typename std::underlying_type<T>::type>(v));
 }
 
-// Captures the result of a CHECK_op and facilitates testing as a boolean.
-class BASE_EXPORT CheckOpResult {
- public:
-  // An empty result signals success.
-  constexpr CheckOpResult() {}
-
-  constexpr explicit CheckOpResult(LogMessage* log_message)
-      : log_message_(log_message) {}
-
-  // Returns true if the check succeeded.
-  constexpr explicit operator bool() const { return !log_message_; }
-
-  LogMessage* log_message() { return log_message_; }
-
-  // TODO(pbos): Annotate this ABSL_ATTRIBUTE_RETURNS_NONNULL after solving
-  // compile failure.
-  // Takes ownership of `v1_str` and `v2_str`, destroying them with free(). For
-  // use with CheckOpValueStr() which allocates these strings using strdup().
-  static LogMessage* CreateLogMessage(bool is_dcheck,
-                                      const char* file,
-                                      int line,
-                                      const char* expr_str,
-                                      char* v1_str,
-                                      char* v2_str);
-
- private:
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #constexpr-ctor-field-initializer
-  RAW_PTR_EXCLUSION LogMessage* const log_message_ = nullptr;
-};
+// Takes ownership of `v1_str` and `v2_str`, destroying them with free(). For
+// use with CheckOpValueStr() which allocates these strings using strdup().
+// Returns allocated string (with strdup) for passing into
+// ::logging::CheckError::(D)CheckOp methods.
+// TODO(pbos): Annotate this ABSL_ATTRIBUTE_RETURNS_NONNULL after solving
+// compile failure.
+BASE_EXPORT char* CreateCheckOpLogMessageString(const char* expr_str,
+                                                char* v1_str,
+                                                char* v2_str);
 
 // Helper macro for binary operators.
 // The 'switch' is used to prevent the 'else' from being ambiguous when the
 // macro is used in an 'if' clause such as:
 // if (a == 1)
 //   CHECK_EQ(2, a);
-#define CHECK_OP_FUNCTION_IMPL(is_dcheck, name, op, val1, val2)         \
-  switch (0)                                                            \
-  case 0:                                                               \
-  default:                                                              \
-    if (::logging::CheckOpResult true_if_passed =                       \
-            ::logging::Check##name##Impl(is_dcheck, __FILE__, __LINE__, \
-                                         (val1), (val2),                \
-                                         #val1 " " #op " " #val2))      \
-      ;                                                                 \
-    else                                                                \
-      ::logging::CheckError(true_if_passed.log_message())
+#define CHECK_OP_FUNCTION_IMPL(check_failure_function, name, op, val1, val2) \
+  switch (0)                                                                 \
+  case 0:                                                                    \
+  default:                                                                   \
+    if (char* const message_on_fail = ::logging::Check##name##Impl(          \
+            (val1), (val2), #val1 " " #op " " #val2);                        \
+        !message_on_fail)                                                    \
+      ;                                                                      \
+    else                                                                     \
+      check_failure_function(message_on_fail)
 
 #if !CHECK_WILL_STREAM()
 
@@ -178,38 +156,33 @@
 #else
 
 #define CHECK_OP(name, op, val1, val2) \
-  CHECK_OP_FUNCTION_IMPL(/*is_dcheck=*/false, name, op, val1, val2)
+  CHECK_OP_FUNCTION_IMPL(::logging::CheckError::CheckOp, name, op, val1, val2)
 
 #endif
 
 // The second overload avoids address-taking of static members for
 // fundamental types.
-#define DEFINE_CHECK_OP_IMPL(name, op)                                      \
-  template <typename T, typename U,                                         \
-            std::enable_if_t<!std::is_fundamental<T>::value ||              \
-                                 !std::is_fundamental<U>::value,            \
-                             int> = 0>                                      \
-  constexpr ::logging::CheckOpResult Check##name##Impl(                     \
-      bool is_dcheck, const char* file, int line, const T& v1, const U& v2, \
-      const char* expr_str) {                                               \
-    if (LIKELY(ANALYZER_ASSUME_TRUE(v1 op v2)))                             \
-      return ::logging::CheckOpResult();                                    \
-    return CheckOpResult(CheckOpResult::CreateLogMessage(                   \
-        is_dcheck, file, line, expr_str, CheckOpValueStr(v1),               \
-        CheckOpValueStr(v2)));                                              \
-  }                                                                         \
-  template <typename T, typename U,                                         \
-            std::enable_if_t<std::is_fundamental<T>::value &&               \
-                                 std::is_fundamental<U>::value,             \
-                             int> = 0>                                      \
-  constexpr ::logging::CheckOpResult Check##name##Impl(                     \
-      bool is_dcheck, const char* file, int line, T v1, U v2,               \
-      const char* expr_str) {                                               \
-    if (LIKELY(ANALYZER_ASSUME_TRUE(v1 op v2)))                             \
-      return ::logging::CheckOpResult();                                    \
-    return CheckOpResult(CheckOpResult::CreateLogMessage(                   \
-        is_dcheck, file, line, expr_str, CheckOpValueStr(v1),               \
-        CheckOpValueStr(v2)));                                              \
+#define DEFINE_CHECK_OP_IMPL(name, op)                                  \
+  template <typename T, typename U,                                     \
+            std::enable_if_t<!std::is_fundamental<T>::value ||          \
+                                 !std::is_fundamental<U>::value,        \
+                             int> = 0>                                  \
+  constexpr char* Check##name##Impl(const T& v1, const U& v2,           \
+                                    const char* expr_str) {             \
+    if (LIKELY(ANALYZER_ASSUME_TRUE(v1 op v2)))                         \
+      return nullptr;                                                   \
+    return CreateCheckOpLogMessageString(expr_str, CheckOpValueStr(v1), \
+                                         CheckOpValueStr(v2));          \
+  }                                                                     \
+  template <typename T, typename U,                                     \
+            std::enable_if_t<std::is_fundamental<T>::value &&           \
+                                 std::is_fundamental<U>::value,         \
+                             int> = 0>                                  \
+  constexpr char* Check##name##Impl(T v1, U v2, const char* expr_str) { \
+    if (LIKELY(ANALYZER_ASSUME_TRUE(v1 op v2)))                         \
+      return nullptr;                                                   \
+    return CreateCheckOpLogMessageString(expr_str, CheckOpValueStr(v1), \
+                                         CheckOpValueStr(v2));          \
   }
 
 // clang-format off
@@ -231,7 +204,7 @@
 #if DCHECK_IS_ON()
 
 #define DCHECK_OP(name, op, val1, val2) \
-  CHECK_OP_FUNCTION_IMPL(/*is_dcheck=*/true, name, op, val1, val2)
+  CHECK_OP_FUNCTION_IMPL(::logging::CheckError::DCheckOp, name, op, val1, val2)
 
 #else
 
diff --git a/base/check_unittest.cc b/base/check_unittest.cc
index 47c0cc51..5098bb3 100644
--- a/base/check_unittest.cc
+++ b/base/check_unittest.cc
@@ -490,7 +490,8 @@
   // logging::NotReachedError::TriggerNotReached() but we have no good way of
   // asserting what that filename or line number is from here.
   EXPECT_LOG_ERROR_WITH_FILENAME("", -1, NOTREACHED() << "foo",
-                                 "Check failed: false. \n");
+                                 "Check failed: false. NOTREACHED log messages "
+                                 "are omitted in official builds. Sorry!\n");
 #endif
   EXPECT_DEATH_IF_SUPPORTED(NotReachedNoreturnInFunction(),
                             CHECK_WILL_STREAM() ? "NOTREACHED hit. " : "");
@@ -517,7 +518,10 @@
 
 #if DCHECK_IS_ON()
   // Expect LOG(ERROR) with streamed params intact.
-  EXPECT_LOG_ERROR(__LINE__, NOTIMPLEMENTED() << "foo", expected_msg + "foo\n");
+  EXPECT_LOG_ERROR_WITH_FILENAME(base::Location::Current().file_name(),
+                                 base::Location::Current().line_number(),
+                                 NOTIMPLEMENTED() << "foo",
+                                 expected_msg + "foo\n");
 #else
   // Expect nothing.
   EXPECT_NO_LOG(NOTIMPLEMENTED() << "foo");
@@ -533,7 +537,9 @@
       "Not implemented reached in void (anonymous namespace)::NiLogOnce()\n";
 
 #if DCHECK_IS_ON()
-  EXPECT_LOG_ERROR(__LINE__ - 8, NiLogOnce(), expected_msg);
+  EXPECT_LOG_ERROR_WITH_FILENAME(base::Location::Current().file_name(),
+                                 base::Location::Current().line_number() - 10,
+                                 NiLogOnce(), expected_msg);
   EXPECT_NO_LOG(NiLogOnce());
 #else
   EXPECT_NO_LOG(NiLogOnce());
diff --git a/base/containers/enum_set.h b/base/containers/enum_set.h
index e6b5315..182f00db 100644
--- a/base/containers/enum_set.h
+++ b/base/containers/enum_set.h
@@ -7,6 +7,7 @@
 
 #include <bitset>
 #include <cstddef>
+#include <initializer_list>
 #include <type_traits>
 #include <utility>
 
@@ -164,6 +165,8 @@
     return bitstring << shift_amount;
   }
 
+  // TODO(crbug/1444105): Deprecated. Use std::initializer_list version below.
+  // Remove once there are no usages.
   template <class... T>
   static constexpr uint64_t bitstring(T... values) {
     uint64_t converted[] = {single_val_bitstring(values)...};
@@ -173,10 +176,23 @@
     return result;
   }
 
+  // TODO(crbug/1444105): Deprecated. Use std::initializer_list version below.
+  // Remove once there are no usages.
   template <class... T>
   constexpr EnumSet(E head, T... tail)
       : EnumSet(EnumBitSet(bitstring(head, tail...))) {}
 
+  static constexpr uint64_t bitstring(const std::initializer_list<E>& values) {
+    uint64_t result = 0;
+    for (E value : values) {
+      result |= single_val_bitstring(value);
+    }
+    return result;
+  }
+
+  constexpr EnumSet(std::initializer_list<E> values)
+      : EnumSet(EnumBitSet(bitstring(values))) {}
+
   // Returns an EnumSet with all possible values.
   static constexpr EnumSet All() {
     return EnumSet(EnumBitSet((1ULL << kValueCount) - 1));
diff --git a/base/containers/enum_set_unittest.cc b/base/containers/enum_set_unittest.cc
index fecd5da..fb3ab1d 100644
--- a/base/containers/enum_set_unittest.cc
+++ b/base/containers/enum_set_unittest.cc
@@ -56,7 +56,7 @@
   static_assert(TestEnumSet::FromRange(TestEnum::TEST_2, TestEnum::TEST_4)
                     .Has(TestEnum::TEST_2),
                 "Expected FromRange() to be integral constant expression");
-  static_assert(TestEnumSet(TestEnum::TEST_2).Has(TestEnum::TEST_2),
+  static_assert(TestEnumSet{TestEnum::TEST_2}.Has(TestEnum::TEST_2),
                 "Expected TestEnumSet() to be integral constant expression");
   static_assert(
       TestEnumSet::FromEnumBitmask(1 << static_cast<uint64_t>(TestEnum::TEST_2))
@@ -65,8 +65,9 @@
   static_assert(
       TestEnumSet::single_val_bitstring(TestEnum::TEST_1) == 1,
       "Expected single_val_bitstring() to be integral constant expression");
-  static_assert(TestEnumSet::bitstring(TestEnum::TEST_1, TestEnum::TEST_2) == 3,
-                "Expected bitstring() to be integral constant expression");
+  static_assert(
+      TestEnumSet::bitstring({TestEnum::TEST_1, TestEnum::TEST_2}) == 3,
+      "Expected bitstring() to be integral constant expression");
 }
 
 TEST_F(EnumSetTest, DefaultConstructor) {
@@ -81,7 +82,7 @@
 }
 
 TEST_F(EnumSetTest, OneArgConstructor) {
-  const TestEnumSet enums(TestEnum::TEST_4);
+  const TestEnumSet enums = {TestEnum::TEST_4};
   EXPECT_FALSE(enums.Empty());
   EXPECT_EQ(static_cast<size_t>(1), enums.Size());
   EXPECT_FALSE(enums.Has(TestEnum::TEST_1));
@@ -92,12 +93,12 @@
 }
 
 TEST_F(EnumSetTest, OneArgConstructorSize) {
-  TestEnumExtremeSet enums(TestEnumExtreme::TEST_0);
+  TestEnumExtremeSet enums = {TestEnumExtreme::TEST_0};
   EXPECT_TRUE(enums.Has(TestEnumExtreme::TEST_0));
 }
 
 TEST_F(EnumSetTest, TwoArgConstructor) {
-  const TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_2);
+  const TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_2};
   EXPECT_FALSE(enums.Empty());
   EXPECT_EQ(static_cast<size_t>(2), enums.Size());
   EXPECT_FALSE(enums.Has(TestEnum::TEST_1));
@@ -108,7 +109,8 @@
 }
 
 TEST_F(EnumSetTest, ThreeArgConstructor) {
-  const TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_2, TestEnum::TEST_1);
+  const TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_2,
+                             TestEnum::TEST_1};
   EXPECT_FALSE(enums.Empty());
   EXPECT_EQ(static_cast<size_t>(3), enums.Size());
   EXPECT_TRUE(enums.Has(TestEnum::TEST_1));
@@ -119,9 +121,10 @@
 }
 
 TEST_F(EnumSetTest, DuplicatesInConstructor) {
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_4, TestEnum::TEST_2, TestEnum::TEST_1,
-                        TestEnum::TEST_4, TestEnum::TEST_2, TestEnum::TEST_4),
-            TestEnumSet(TestEnum::TEST_1, TestEnum::TEST_2, TestEnum::TEST_4));
+  EXPECT_EQ(
+      TestEnumSet({TestEnum::TEST_4, TestEnum::TEST_2, TestEnum::TEST_1,
+                   TestEnum::TEST_4, TestEnum::TEST_2, TestEnum::TEST_4}),
+      TestEnumSet({TestEnum::TEST_1, TestEnum::TEST_2, TestEnum::TEST_4}));
 }
 
 TEST_F(EnumSetTest, All) {
@@ -136,71 +139,71 @@
 }
 
 TEST_F(EnumSetTest, FromRange) {
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_2, TestEnum::TEST_3, TestEnum::TEST_4),
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_2, TestEnum::TEST_3, TestEnum::TEST_4}),
             TestEnumSet::FromRange(TestEnum::TEST_2, TestEnum::TEST_4));
   EXPECT_EQ(TestEnumSet::All(),
             TestEnumSet::FromRange(TestEnum::TEST_1, TestEnum::TEST_5));
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_2),
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_2}),
             TestEnumSet::FromRange(TestEnum::TEST_2, TestEnum::TEST_2));
 
   using RestrictedRangeSet =
       EnumSet<TestEnum, TestEnum::TEST_2, TestEnum::TEST_MAX>;
-  EXPECT_EQ(
-      RestrictedRangeSet(TestEnum::TEST_2, TestEnum::TEST_3, TestEnum::TEST_4),
-      RestrictedRangeSet::FromRange(TestEnum::TEST_2, TestEnum::TEST_4));
+  EXPECT_EQ(RestrictedRangeSet(
+                {TestEnum::TEST_2, TestEnum::TEST_3, TestEnum::TEST_4}),
+            RestrictedRangeSet::FromRange(TestEnum::TEST_2, TestEnum::TEST_4));
   EXPECT_EQ(RestrictedRangeSet::All(),
             RestrictedRangeSet::FromRange(TestEnum::TEST_2, TestEnum::TEST_5));
 }
 
 TEST_F(EnumSetTest, Put) {
-  TestEnumSet enums(TestEnum::TEST_4);
+  TestEnumSet enums = {TestEnum::TEST_4};
   enums.Put(TestEnum::TEST_3);
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4), enums);
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_3, TestEnum::TEST_4}), enums);
   enums.Put(TestEnum::TEST_5);
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4, TestEnum::TEST_5),
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_3, TestEnum::TEST_4, TestEnum::TEST_5}),
             enums);
 }
 
 TEST_F(EnumSetTest, PutAll) {
-  TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_5);
-  enums.PutAll(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4));
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4, TestEnum::TEST_5),
+  TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_5};
+  enums.PutAll({TestEnum::TEST_3, TestEnum::TEST_4});
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_3, TestEnum::TEST_4, TestEnum::TEST_5}),
             enums);
 }
 
 TEST_F(EnumSetTest, PutRange) {
   TestEnumSet enums;
   enums.PutRange(TestEnum::TEST_2, TestEnum::TEST_4);
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_2, TestEnum::TEST_3, TestEnum::TEST_4),
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_2, TestEnum::TEST_3, TestEnum::TEST_4}),
             enums);
 }
 
 TEST_F(EnumSetTest, RetainAll) {
-  TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_5);
-  enums.RetainAll(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4));
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_4), enums);
+  TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_5};
+  enums.RetainAll(TestEnumSet({TestEnum::TEST_3, TestEnum::TEST_4}));
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_4}), enums);
 }
 
 TEST_F(EnumSetTest, Remove) {
-  TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_5);
+  TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_5};
   enums.Remove(TestEnum::TEST_1);
   enums.Remove(TestEnum::TEST_3);
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_4, TestEnum::TEST_5), enums);
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_4, TestEnum::TEST_5}), enums);
   enums.Remove(TestEnum::TEST_4);
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_5), enums);
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_5}), enums);
   enums.Remove(TestEnum::TEST_5);
   enums.Remove(TestEnum::TEST_6_OUT_OF_BOUNDS);
   EXPECT_TRUE(enums.Empty());
 }
 
 TEST_F(EnumSetTest, RemoveAll) {
-  TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_5);
-  enums.RemoveAll(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4));
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_5), enums);
+  TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_5};
+  enums.RemoveAll(TestEnumSet({TestEnum::TEST_3, TestEnum::TEST_4}));
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_5}), enums);
 }
 
 TEST_F(EnumSetTest, Clear) {
-  TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_5);
+  TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_5};
   enums.Clear();
   EXPECT_TRUE(enums.Empty());
 }
@@ -213,19 +216,19 @@
   EXPECT_TRUE(enums.Empty());
 
   enums.PutOrRemove(TestEnum::TEST_4, true);
-  EXPECT_EQ(enums, TestEnumSet(TestEnum::TEST_4));
+  EXPECT_EQ(enums, TestEnumSet({TestEnum::TEST_4}));
 
   enums.PutOrRemove(TestEnum::TEST_5, true);
-  EXPECT_EQ(enums, TestEnumSet(TestEnum::TEST_4, TestEnum::TEST_5));
+  EXPECT_EQ(enums, TestEnumSet({TestEnum::TEST_4, TestEnum::TEST_5}));
   enums.PutOrRemove(TestEnum::TEST_5, true);
-  EXPECT_EQ(enums, TestEnumSet(TestEnum::TEST_4, TestEnum::TEST_5));
+  EXPECT_EQ(enums, TestEnumSet({TestEnum::TEST_4, TestEnum::TEST_5}));
 
   enums.PutOrRemove(TestEnum::TEST_4, false);
-  EXPECT_EQ(enums, TestEnumSet(TestEnum::TEST_5));
+  EXPECT_EQ(enums, TestEnumSet({TestEnum::TEST_5}));
 }
 
 TEST_F(EnumSetTest, Has) {
-  const TestEnumSet enums(TestEnum::TEST_4, TestEnum::TEST_5);
+  const TestEnumSet enums = {TestEnum::TEST_4, TestEnum::TEST_5};
   EXPECT_FALSE(enums.Has(TestEnum::TEST_1));
   EXPECT_FALSE(enums.Has(TestEnum::TEST_2));
   EXPECT_FALSE(enums.Has(TestEnum::TEST_3));
@@ -235,8 +238,8 @@
 }
 
 TEST_F(EnumSetTest, HasAll) {
-  const TestEnumSet enums1(TestEnum::TEST_4, TestEnum::TEST_5);
-  const TestEnumSet enums2(TestEnum::TEST_3, TestEnum::TEST_4);
+  const TestEnumSet enums1 = {TestEnum::TEST_4, TestEnum::TEST_5};
+  const TestEnumSet enums2 = {TestEnum::TEST_3, TestEnum::TEST_4};
   const TestEnumSet enums3 = Union(enums1, enums2);
   EXPECT_TRUE(enums1.HasAll(enums1));
   EXPECT_FALSE(enums1.HasAll(enums2));
@@ -252,9 +255,9 @@
 }
 
 TEST_F(EnumSetTest, HasAny) {
-  const TestEnumSet enums1(TestEnum::TEST_4, TestEnum::TEST_5);
-  const TestEnumSet enums2(TestEnum::TEST_3, TestEnum::TEST_4);
-  const TestEnumSet enums3(TestEnum::TEST_1, TestEnum::TEST_2);
+  const TestEnumSet enums1 = {TestEnum::TEST_4, TestEnum::TEST_5};
+  const TestEnumSet enums2 = {TestEnum::TEST_3, TestEnum::TEST_4};
+  const TestEnumSet enums3 = {TestEnum::TEST_1, TestEnum::TEST_2};
   EXPECT_TRUE(enums1.HasAny(enums1));
   EXPECT_TRUE(enums1.HasAny(enums2));
   EXPECT_FALSE(enums1.HasAny(enums3));
@@ -269,7 +272,7 @@
 }
 
 TEST_F(EnumSetTest, Iterators) {
-  const TestEnumSet enums1(TestEnum::TEST_4, TestEnum::TEST_5);
+  const TestEnumSet enums1 = {TestEnum::TEST_4, TestEnum::TEST_5};
   TestEnumSet enums2;
   for (TestEnum e : enums1) {
     enums2.Put(e);
@@ -278,7 +281,7 @@
 }
 
 TEST_F(EnumSetTest, RangeBasedForLoop) {
-  const TestEnumSet enums1(TestEnum::TEST_2, TestEnum::TEST_5);
+  const TestEnumSet enums1 = {TestEnum::TEST_2, TestEnum::TEST_5};
   TestEnumSet enums2;
   for (TestEnum e : enums1) {
     enums2.Put(e);
@@ -287,7 +290,7 @@
 }
 
 TEST_F(EnumSetTest, IteratorComparisonOperators) {
-  const TestEnumSet enums(TestEnum::TEST_2, TestEnum::TEST_4);
+  const TestEnumSet enums = {TestEnum::TEST_2, TestEnum::TEST_4};
   const auto first_it = enums.begin();
   const auto second_it = ++enums.begin();
 
@@ -308,7 +311,7 @@
 }
 
 TEST_F(EnumSetTest, IteratorIncrementOperators) {
-  const TestEnumSet enums(TestEnum::TEST_2, TestEnum::TEST_4);
+  const TestEnumSet enums = {TestEnum::TEST_2, TestEnum::TEST_4};
   const auto begin = enums.begin();
 
   auto post_inc_it = begin;
@@ -339,28 +342,28 @@
 }
 
 TEST_F(EnumSetTest, Union) {
-  const TestEnumSet enums1(TestEnum::TEST_4, TestEnum::TEST_5);
-  const TestEnumSet enums2(TestEnum::TEST_3, TestEnum::TEST_4);
+  const TestEnumSet enums1 = {TestEnum::TEST_4, TestEnum::TEST_5};
+  const TestEnumSet enums2 = {TestEnum::TEST_3, TestEnum::TEST_4};
   const TestEnumSet enums3 = Union(enums1, enums2);
 
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_3, TestEnum::TEST_4, TestEnum::TEST_5),
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_3, TestEnum::TEST_4, TestEnum::TEST_5}),
             enums3);
 }
 
 TEST_F(EnumSetTest, Intersection) {
-  const TestEnumSet enums1(TestEnum::TEST_4, TestEnum::TEST_5);
-  const TestEnumSet enums2(TestEnum::TEST_3, TestEnum::TEST_4);
+  const TestEnumSet enums1 = {TestEnum::TEST_4, TestEnum::TEST_5};
+  const TestEnumSet enums2 = {TestEnum::TEST_3, TestEnum::TEST_4};
   const TestEnumSet enums3 = Intersection(enums1, enums2);
 
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_4), enums3);
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_4}), enums3);
 }
 
 TEST_F(EnumSetTest, Difference) {
-  const TestEnumSet enums1(TestEnum::TEST_4, TestEnum::TEST_5);
-  const TestEnumSet enums2(TestEnum::TEST_3, TestEnum::TEST_4);
+  const TestEnumSet enums1 = {TestEnum::TEST_4, TestEnum::TEST_5};
+  const TestEnumSet enums2 = {TestEnum::TEST_3, TestEnum::TEST_4};
   const TestEnumSet enums3 = Difference(enums1, enums2);
 
-  EXPECT_EQ(TestEnumSet(TestEnum::TEST_5), enums3);
+  EXPECT_EQ(TestEnumSet({TestEnum::TEST_5}), enums3);
 }
 
 TEST_F(EnumSetTest, ToFromEnumBitmask) {
@@ -368,12 +371,12 @@
   EXPECT_EQ(empty.ToEnumBitmask(), 0ULL);
   EXPECT_EQ(TestEnumSet::FromEnumBitmask(0), empty);
 
-  const TestEnumSet enums1(TestEnum::TEST_2);
+  const TestEnumSet enums1 = {TestEnum::TEST_2};
   const uint64_t val1 = 1ULL << static_cast<uint64_t>(TestEnum::TEST_2);
   EXPECT_EQ(enums1.ToEnumBitmask(), val1);
   EXPECT_EQ(TestEnumSet::FromEnumBitmask(val1), enums1);
 
-  const TestEnumSet enums2(TestEnum::TEST_3, TestEnum::TEST_4);
+  const TestEnumSet enums2 = {TestEnum::TEST_3, TestEnum::TEST_4};
   const uint64_t val2 = 1ULL << static_cast<uint64_t>(TestEnum::TEST_3) |
                         1ULL << static_cast<uint64_t>(TestEnum::TEST_4);
   EXPECT_EQ(enums2.ToEnumBitmask(), val2);
@@ -385,7 +388,7 @@
   EXPECT_EQ(empty.ToEnumBitmask(), 0ULL);
   EXPECT_EQ(TestEnumExtremeSet::FromEnumBitmask(0ULL), empty);
 
-  const TestEnumExtremeSet enums1(TestEnumExtreme::TEST_63);
+  const TestEnumExtremeSet enums1 = {TestEnumExtreme::TEST_63};
   const uint64_t val1 = 1ULL << static_cast<uint64_t>(TestEnumExtreme::TEST_63);
   EXPECT_EQ(enums1.ToEnumBitmask(), val1);
   EXPECT_EQ(TestEnumExtremeSet::FromEnumBitmask(val1), enums1);
@@ -393,12 +396,12 @@
 
 TEST_F(EnumSetTest, FromEnumBitmaskIgnoresExtraBits) {
   const TestEnumSet kSets[] = {
-      TestEnumSet(),
-      TestEnumSet(TestEnum::TEST_MIN),
-      TestEnumSet(TestEnum::TEST_MAX),
-      TestEnumSet(TestEnum::TEST_MIN, TestEnum::TEST_MAX),
-      TestEnumSet(TestEnum::TEST_MIN, TestEnum::TEST_MAX),
-      TestEnumSet(TestEnum::TEST_2, TestEnum::TEST_4),
+      {},
+      {TestEnum::TEST_MIN},
+      {TestEnum::TEST_MAX},
+      {TestEnum::TEST_MIN, TestEnum::TEST_MAX},
+      {TestEnum::TEST_MIN, TestEnum::TEST_MAX},
+      {TestEnum::TEST_2, TestEnum::TEST_4},
   };
   size_t i = 0;
   for (const TestEnumSet& set : kSets) {
@@ -575,9 +578,9 @@
 
 TEST_F(EnumSetDeathTest, VariadicConstructorCrashesOnOutOfRange) {
   // Constructor should crash given out-of-range values.
-  EXPECT_CHECK_DEATH(TestEnumSet(TestEnum::TEST_BELOW_MIN).Empty());
-  EXPECT_CHECK_DEATH(TestEnumSet(TestEnum::TEST_BELOW_MIN_NEGATIVE).Empty());
-  EXPECT_CHECK_DEATH(TestEnumSet(TestEnum::TEST_6_OUT_OF_BOUNDS).Empty());
+  EXPECT_CHECK_DEATH(TestEnumSet({TestEnum::TEST_BELOW_MIN}).Empty());
+  EXPECT_CHECK_DEATH(TestEnumSet({TestEnum::TEST_BELOW_MIN_NEGATIVE}).Empty());
+  EXPECT_CHECK_DEATH(TestEnumSet({TestEnum::TEST_6_OUT_OF_BOUNDS}).Empty());
 }
 
 TEST_F(EnumSetDeathTest, FromRangeCrashesOnBadInputs) {
diff --git a/base/debug/allocation_trace.cc b/base/debug/allocation_trace.cc
index 4087534..ac8134ee 100644
--- a/base/debug/allocation_trace.cc
+++ b/base/debug/allocation_trace.cc
@@ -55,10 +55,6 @@
   }
 }
 
-bool AllocationTraceRecorder::IsValid() const {
-  return kMemoryGuard == prologue_ && kMemoryGuard == epilogue_;
-}
-
 size_t AllocationTraceRecorder::size() const {
   return std::min(kMaximumNumberOfMemoryOperationTraces,
                   total_number_of_records_.load(std::memory_order_relaxed));
diff --git a/base/debug/allocation_trace.h b/base/debug/allocation_trace.h
index 6413c69..5110bdd 100644
--- a/base/debug/allocation_trace.h
+++ b/base/debug/allocation_trace.h
@@ -182,17 +182,6 @@
 // TODO(https://crbug.com/1419908): Evaluate the impact of the shared cache
 // lines between entries.
 struct BASE_EXPORT AllocationTraceRecorder {
-  // Verify that the image of a recorder copied into the crashpad handler is
-  // still valid. IsValid compares the bytes of prologue and epilogue to
-  // expected values.
-  //
-  // Note: This is intended to protect from programming errors like using a
-  // wrong address or not copying the full recorder into the crash handler.
-  //
-  // TODO(https://crbug.com/1419908): Develop a full integration test which
-  // makes this IsValid check obsolete.
-  bool IsValid() const;
-
   // The allocation event observer interface. See the dispatcher for further
   // details. The functions are marked NO_INLINE. All other functions called but
   // the one taking the call stack are marked ALWAYS_INLINE. This way we ensure
@@ -229,30 +218,17 @@
   }
 
  private:
-  // The guards are short byte sequences which are stored in the beginning and
-  // at the end of AllocationTraceRecorder. They are used to ensure the memory
-  // image copied to crashpad handler is valid.
-  static constexpr uint64_t kMemoryGuard = 0x5A55A55A55A55A5A;
-
   ALWAYS_INLINE size_t GetNextIndex();
 
   ALWAYS_INLINE static constexpr size_t WrapIdxIfNeeded(size_t idx);
 
-  // Used to ensure validity after copying memory-image into crashpad-handler.
-  // It is declared volatile to prevent the compiler from taking shortcuts
-  // when checking the guards, since they are never written explicitly.
-  uint64_t const volatile prologue_ = kMemoryGuard;
   // The actual container.
   std::array<OperationRecord, kMaximumNumberOfMemoryOperationTraces>
-      alloc_trace_buffer_;
+      alloc_trace_buffer_ = {};
   // The total number of records that have been taken so far. Note that this
   // might be greater than |kMaximumNumberOfMemoryOperationTraces| since we
   // overwrite oldest items.
   std::atomic<size_t> total_number_of_records_ = 0;
-  // Used to ensure validity after copying memory-image into crashpad-handler.
-  // It is declared volatile to prevent the compiler from taking shortcuts
-  // when checking the guards, since they are never written explicitly.
-  uint64_t const volatile epilogue_ = kMemoryGuard;
 };
 
 ALWAYS_INLINE constexpr size_t AllocationTraceRecorder::WrapIdxIfNeeded(
diff --git a/base/debug/allocation_trace_unittest.cc b/base/debug/allocation_trace_unittest.cc
index 304f01c..71886bf7 100644
--- a/base/debug/allocation_trace_unittest.cc
+++ b/base/debug/allocation_trace_unittest.cc
@@ -3,10 +3,6 @@
 // found in the LICENSE file.
 
 #include "base/debug/allocation_trace.h"
-#include "base/allocator/dispatcher/dispatcher.h"
-#include "base/debug/stack_trace.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
 
 #include <algorithm>
 #include <cstddef>
@@ -15,10 +11,17 @@
 #include <sstream>
 #include <string>
 
+#include "base/allocator/dispatcher/dispatcher.h"
+#include "base/debug/stack_trace.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
 using base::allocator::dispatcher::AllocationSubsystem;
-using testing::AssertionResult;
+using testing::ContainerEq;
+using testing::Message;
 using testing::Test;
 
+namespace base::debug::tracer {
 namespace {
 
 template <typename Iterator>
@@ -41,12 +44,16 @@
   return MakeString(std::begin(data), std::end(data));
 }
 
+void AreEqual(const base::debug::tracer::OperationRecord& expected,
+              const base::debug::tracer::OperationRecord& is) {
+  EXPECT_EQ(is.GetOperationType(), expected.GetOperationType());
+  EXPECT_EQ(is.GetAddress(), expected.GetAddress());
+  EXPECT_EQ(is.GetSize(), expected.GetSize());
+  EXPECT_THAT(is.GetStackTrace(), ContainerEq(expected.GetStackTrace()));
+}
+
 }  // namespace
 
-namespace base::debug::tracer {
-
-using base::allocator::dispatcher::AllocationSubsystem;
-
 struct AllocationTraceRecorderTest : public Test {
   AllocationTraceRecorder& GetSubjectUnderTest() const {
     return *subject_under_test_;
@@ -68,9 +75,25 @@
       std::make_unique<AllocationTraceRecorder>();
 };
 
-TEST_F(AllocationTraceRecorderTest, VerifyIsValid) {
+TEST_F(AllocationTraceRecorderTest, VerifyBinaryCopy) {
   AllocationTraceRecorder& subject_under_test = GetSubjectUnderTest();
 
+  // Fill the recorder with some fake allocations and frees.
+  constexpr size_t number_of_records = 100;
+
+  for (size_t index = 0; index < number_of_records; ++index) {
+    if (index & 0x1) {
+      subject_under_test.OnAllocation(this, sizeof(*this),
+                                      AllocationSubsystem::kPartitionAllocator,
+                                      nullptr);
+    } else {
+      subject_under_test.OnFree(this);
+    }
+  }
+
+  ASSERT_EQ(number_of_records, subject_under_test.size());
+
+  // Create a copy of the recorder using buffer as storage for the copy.
   auto const buffer = std::make_unique<Buffer>();
 
   ASSERT_TRUE(buffer);
@@ -78,29 +101,15 @@
   auto* const buffered_recorder =
       reinterpret_cast<AllocationTraceRecorder*>(&(buffer->data[0]));
 
-  // Verify IsValid returns true on the copied image.
-  {
-    memcpy(buffered_recorder, &subject_under_test,
-           sizeof(AllocationTraceRecorder));
-    EXPECT_TRUE(buffered_recorder->IsValid());
-  }
+  memcpy(buffered_recorder, &subject_under_test,
+         sizeof(AllocationTraceRecorder));
 
-  // Verify IsValid returns false when the prologue has been altered on the
-  // copied image.
-  {
-    memcpy(buffered_recorder, &subject_under_test,
-           sizeof(AllocationTraceRecorder));
-    buffer->data[2] ^= 0xff;
-    EXPECT_FALSE(buffered_recorder->IsValid());
-  }
+  // Verify that the original recorder and the buffered recorder are equal.
+  ASSERT_EQ(subject_under_test.size(), buffered_recorder->size());
 
-  // Verify IsValid returns false when the epilogue has been altered on the
-  // copied image.
-  {
-    memcpy(buffered_recorder, &subject_under_test,
-           sizeof(AllocationTraceRecorder));
-    buffer->data[sizeof(AllocationTraceRecorder) - 2] ^= 0xff;
-    EXPECT_FALSE(buffered_recorder->IsValid());
+  for (size_t index = 0; index < subject_under_test.size(); ++index) {
+    SCOPED_TRACE(Message("difference detected at index ") << index);
+    AreEqual(subject_under_test[index], (*buffered_recorder)[index]);
   }
 }
 
diff --git a/base/debug/debug.gni b/base/debug/debug.gni
index 848d61a2..f13f156 100644
--- a/base/debug/debug.gni
+++ b/base/debug/debug.gni
@@ -14,7 +14,7 @@
   #
   # Although it should work on other platforms as well, for the above reasons,
   # we currently enable it only for Android when compiling for Arm64.
-  build_allocation_stack_trace_recorder = false
+  build_allocation_stack_trace_recorder = current_cpu == "arm64" && is_android
 }
 
 assert(!(build_allocation_stack_trace_recorder && is_fuchsia),
diff --git a/base/mac/foundation_util.h b/base/mac/foundation_util.h
index 159d1ff..bbdaba9 100644
--- a/base/mac/foundation_util.h
+++ b/base/mac/foundation_util.h
@@ -135,7 +135,7 @@
 }  // namespace base::mac
 
 // These casting functions cannot be implemented in a way that will work with
-// ARC. Use the casting functions in base/mac/bridging.h instead.
+// ARC. Use the casting functions in base/apple/bridging.h instead.
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 
 #if defined(__OBJC__)
diff --git a/base/memory/ref_counted.h b/base/memory/ref_counted.h
index f70e307..6658dba 100644
--- a/base/memory/ref_counted.h
+++ b/base/memory/ref_counted.h
@@ -16,8 +16,6 @@
 #include "base/check_op.h"
 #include "base/compiler_specific.h"
 #include "base/dcheck_is_on.h"
-// TODO(dcheng): Remove this separately.
-#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
 #include "base/template_util.h"
diff --git a/base/notreached.h b/base/notreached.h
index 96d4f9b..6db49e1f 100644
--- a/base/notreached.h
+++ b/base/notreached.h
@@ -37,8 +37,7 @@
 // TODO(crbug.com/851128): Rename back to NOTREACHED() once there are no callers
 // of the old non-CHECK-fatal macro.
 #if CHECK_WILL_STREAM()
-#define NOTREACHED_NORETURN() \
-  ::logging::NotReachedNoreturnError(__FILE__, __LINE__)
+#define NOTREACHED_NORETURN() ::logging::NotReachedNoreturnError()
 #else
 // This function is used to be able to detect NOTREACHED() failures in stack
 // traces where this symbol is preserved (even if inlined). Its implementation
@@ -60,7 +59,7 @@
 // NOTIMPLEMENTED_LOG_ONCE() << "foo message"; pattern is not supported.
 #if DCHECK_IS_ON()
 #define NOTIMPLEMENTED() \
-  ::logging::CheckError::NotImplemented(__FILE__, __LINE__, __PRETTY_FUNCTION__)
+  ::logging::CheckError::NotImplemented(__PRETTY_FUNCTION__)
 #else
 #define NOTIMPLEMENTED() EAT_CHECK_STREAM_PARAMS()
 #endif
diff --git a/base/task/sequence_manager/thread_controller.cc b/base/task/sequence_manager/thread_controller.cc
index 8667d47..822dd6c1 100644
--- a/base/task/sequence_manager/thread_controller.cc
+++ b/base/task/sequence_manager/thread_controller.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "base/task/sequence_manager/thread_controller.h"
+#include <atomic>
 
 #include "base/check.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram.h"
 #include "base/metrics/histogram_base.h"
 #include "base/notreached.h"
@@ -16,6 +18,21 @@
 namespace sequence_manager {
 namespace internal {
 
+namespace {
+// Control whether sample metadata is recorded in this class. Enabled by
+// default to ensure never losing data.
+BASE_FEATURE(kThreadControllerSetsProfilerMetadata,
+             "ThreadControllerSetsProfilerMetadata",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+// Thread safe copy to be updated once feature list is available. This
+// defaults to true to make sure that no metadata is lost on clients that
+// need to record. This leads to some overeporting before feature list
+// initialization on other clients but that's still way better than the current
+// situation which is reporting all the time.
+std::atomic<bool> g_thread_controller_sets_profiler_metadata{true};
+}  // namespace
+
 ThreadController::ThreadController(const TickClock* time_source)
     : associated_thread_(AssociatedThreadId::CreateUnbound()),
       time_source_(time_source) {}
@@ -38,6 +55,18 @@
   DCHECK_EQ(run_levels_.size(), 0u);
 }
 
+// static
+void ThreadController::InitializeFeatures() {
+  g_thread_controller_sets_profiler_metadata.store(
+      base::FeatureList::IsEnabled(kThreadControllerSetsProfilerMetadata),
+      std::memory_order_relaxed);
+}
+
+bool ThreadController::RunLevelTracker::RunLevel::ShouldRecordSampleMetadata() {
+  return g_thread_controller_sets_profiler_metadata.load(
+      std::memory_order_relaxed);
+}
+
 void ThreadController::EnableMessagePumpTimeKeeperMetrics(
     const char* thread_name) {
   // MessagePump runs too fast, a low-res clock would result in noisy metrics.
@@ -247,12 +276,14 @@
       // applied on the final pop().
       time_keeper_->RecordEndOfPhase(kNested, *exit_lazy_now_);
 
-      // Intentionally ordered after UpdateState(kIdle), reinstantiates
-      // thread_controller_sample_metadata_ when yielding back to a parent
-      // RunLevel (which is active by definition as it is currently running this
-      // one).
-      thread_controller_sample_metadata_.Set(
-          static_cast<int64_t>(++thread_controller_active_id_));
+      if (ShouldRecordSampleMetadata()) {
+        // Intentionally ordered after UpdateState(kIdle), reinstantiates
+        // thread_controller_sample_metadata_ when yielding back to a parent
+        // RunLevel (which is active by definition as it is currently running
+        // this one).
+        thread_controller_sample_metadata_.Set(
+            static_cast<int64_t>(++thread_controller_active_id_));
+      }
     }
   }
 }
@@ -279,12 +310,17 @@
     // ThreadController::RunLevelTracker::RecordScheduleWork.
     TRACE_EVENT_BEGIN("base", "ThreadController active",
                       *terminating_wakeup_flow_lambda_);
-    // Overriding the annotation from the previous RunLevel is intentional. Only
-    // the top RunLevel is ever updated, which holds the relevant state.
-    thread_controller_sample_metadata_.Set(
-        static_cast<int64_t>(++thread_controller_active_id_));
+
+    if (ShouldRecordSampleMetadata()) {
+      // Overriding the annotation from the previous RunLevel is intentional.
+      // Only the top RunLevel is ever updated, which holds the relevant state.
+      thread_controller_sample_metadata_.Set(
+          static_cast<int64_t>(++thread_controller_active_id_));
+    }
   } else {
-    thread_controller_sample_metadata_.Remove();
+    if (ShouldRecordSampleMetadata()) {
+      thread_controller_sample_metadata_.Remove();
+    }
     TRACE_EVENT_END("base");
     // TODO(crbug.com/1021571): Remove this once fixed.
     PERFETTO_INTERNAL_ADD_EMPTY_EVENT();
diff --git a/base/task/sequence_manager/thread_controller.h b/base/task/sequence_manager/thread_controller.h
index 138bdcd..d7cb065 100644
--- a/base/task/sequence_manager/thread_controller.h
+++ b/base/task/sequence_manager/thread_controller.h
@@ -145,6 +145,10 @@
   virtual void DetachFromMessagePump() = 0;
 #endif
 
+  // Initializes the state of all the thread controller features. Must be
+  // invoked after FeatureList initialization.
+  static void InitializeFeatures();
+
   // Enables TimeKeeper metrics. `thread_name` will be used as a suffix.
   void EnableMessagePumpTimeKeeperMetrics(const char* thread_name);
 
@@ -400,6 +404,8 @@
       State state_ = kIdle;
       bool is_nested_;
 
+      bool ShouldRecordSampleMetadata();
+
       const raw_ref<TimeKeeper> time_keeper_;
       // Must be set shortly before ~RunLevel.
       raw_ptr<LazyNow> exit_lazy_now_ = nullptr;
diff --git a/base/task/single_thread_task_runner.h b/base/task/single_thread_task_runner.h
index b4409c9..e89ca0d 100644
--- a/base/task/single_thread_task_runner.h
+++ b/base/task/single_thread_task_runner.h
@@ -8,6 +8,7 @@
 #include "base/auto_reset.h"
 #include "base/base_export.h"
 #include "base/dcheck_is_on.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/task/sequenced_task_runner.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index 1f5a211..cce5e76 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -513,7 +513,10 @@
   optional int32 browsing_instance_id = 1;
 
   // The ID of the CoopRelatedGroup that the BrowsingContextState belongs to.
-  optional int32 coop_related_group_id = 2;
+  optional int32 coop_related_group_id = 2 [deprecated = true];
+
+  // The token of the CoopRelatedGroup that the BrowsingContextState belongs to.
+  optional string coop_related_group_token = 3;
 
   // Additional untyped debug information associated with this
   // FrameTreeNode, populated via TracedProto::AddDebugAnnotations API.
diff --git a/base/values_unittest.cc b/base/values_unittest.cc
index b5c578d6..0b99a937 100644
--- a/base/values_unittest.cc
+++ b/base/values_unittest.cc
@@ -1519,82 +1519,40 @@
 }
 
 TEST(ValuesTest, Clone) {
-  Value::Dict original_dict;
-  Value* null_weak = original_dict.Set("null", Value());
-  Value* bool_weak = original_dict.Set("bool", Value(true));
-  Value* int_weak = original_dict.Set("int", Value(42));
-  Value* double_weak = original_dict.Set("double", Value(3.14));
-  Value* string_weak = original_dict.Set("string", Value("hello"));
-  Value* string16_weak = original_dict.Set("string16", Value(u"hello16"));
-
-  Value* binary_weak =
-      original_dict.Set("binary", Value(Value::BlobStorage(42, '!')));
+  Value original_null;
+  Value original_bool(true);
+  Value original_int(42);
+  Value original_double(3.14);
+  Value original_string("hello");
+  Value original_string16(u"hello16");
+  Value original_binary(Value::BlobStorage(42, '!'));
 
   Value::List list;
   list.Append(0);
   list.Append(1);
-  Value* list_weak = original_dict.Set("list", Value(std::move(list)));
+  Value original_list(std::move(list));
 
-  Value* dict_weak = original_dict.Set("dictionary", Value(Value::Type::DICT));
-  dict_weak->GetDict().Set("key", "value");
+  Value original_dict(Value::Dict()
+                          .Set("null", original_null.Clone())
+                          .Set("bool", original_bool.Clone())
+                          .Set("int", original_int.Clone())
+                          .Set("double", original_double.Clone())
+                          .Set("string", original_string.Clone())
+                          .Set("string16", original_string16.Clone())
+                          .Set("binary", original_binary.Clone())
+                          .Set("list", original_list.Clone()));
 
-  Value::Dict copy_dict = original_dict.Clone();
-
-  Value* copy_null = copy_dict.Find("null");
-  ASSERT_TRUE(copy_null);
-  ASSERT_NE(copy_null, null_weak);
-  ASSERT_TRUE(copy_null->is_none());
-
-  Value* copy_bool = copy_dict.Find("bool");
-  ASSERT_TRUE(copy_bool);
-  ASSERT_NE(copy_bool, bool_weak);
-  ASSERT_TRUE(copy_bool->is_bool());
-  ASSERT_TRUE(copy_bool->GetBool());
-
-  Value* copy_int = copy_dict.Find("int");
-  ASSERT_TRUE(copy_int);
-  ASSERT_NE(copy_int, int_weak);
-  ASSERT_TRUE(copy_int->is_int());
-  ASSERT_EQ(42, copy_int->GetInt());
-
-  Value* copy_double = copy_dict.Find("double");
-  ASSERT_TRUE(copy_double);
-  ASSERT_NE(copy_double, double_weak);
-  ASSERT_TRUE(copy_double->is_double());
-  ASSERT_EQ(3.14, copy_double->GetDouble());
-
-  Value* copy_string = copy_dict.Find("string");
-  ASSERT_TRUE(copy_string);
-  ASSERT_NE(copy_string, string_weak);
-  ASSERT_TRUE(copy_string->is_string());
-  ASSERT_EQ(std::string("hello"), copy_string->GetString());
-
-  Value* copy_string16 = copy_dict.Find("string16");
-  ASSERT_TRUE(copy_string16);
-  ASSERT_NE(copy_string16, string16_weak);
-  ASSERT_TRUE(copy_string16->is_string());
-  ASSERT_EQ(std::string("hello16"), copy_string16->GetString());
-
-  Value* copy_binary = copy_dict.Find("binary");
-  ASSERT_TRUE(copy_binary);
-  ASSERT_NE(copy_binary, binary_weak);
-  ASSERT_TRUE(copy_binary->is_blob());
-  ASSERT_NE(binary_weak->GetBlob().data(), copy_binary->GetBlob().data());
-  ASSERT_EQ(binary_weak->GetBlob(), copy_binary->GetBlob());
-
-  Value* copy_value = copy_dict.Find("list");
-  ASSERT_TRUE(copy_value);
-  ASSERT_NE(copy_value, list_weak);
-  ASSERT_TRUE(copy_value->is_list());
-  ASSERT_EQ(2U, copy_value->GetList().size());
-
-  copy_value = copy_dict.Find("dictionary");
-  ASSERT_TRUE(copy_value);
-  ASSERT_NE(copy_value, dict_weak);
-  ASSERT_TRUE(copy_value->is_dict());
-  Value::Dict* copy_nested_dictionary = copy_value->GetIfDict();
-  ASSERT_TRUE(copy_nested_dictionary);
-  EXPECT_TRUE(copy_nested_dictionary->Find("key"));
+  Value copy_value = original_dict.Clone();
+  const Value::Dict& copy_dict = copy_value.GetDict();
+  EXPECT_EQ(original_dict, copy_dict);
+  EXPECT_EQ(original_null, *copy_dict.Find("null"));
+  EXPECT_EQ(original_bool, *copy_dict.Find("bool"));
+  EXPECT_EQ(original_int, *copy_dict.Find("int"));
+  EXPECT_EQ(original_double, *copy_dict.Find("double"));
+  EXPECT_EQ(original_string, *copy_dict.Find("string"));
+  EXPECT_EQ(original_string16, *copy_dict.Find("string16"));
+  EXPECT_EQ(original_binary, *copy_dict.Find("binary"));
+  EXPECT_EQ(original_list, *copy_dict.Find("list"));
 }
 
 TEST(ValuesTest, TakeString) {
@@ -1906,42 +1864,6 @@
   }
 }
 
-TEST(ValuesTest, DeepCopyCovariantReturnTypes) {
-  Value::Dict original_dict;
-  Value* null_weak = original_dict.Set("null", Value());
-  Value* bool_weak = original_dict.Set("bool", true);
-  Value* int_weak = original_dict.Set("int", 42);
-  Value* double_weak = original_dict.Set("double", 3.14);
-  Value* string_weak = original_dict.Set("string", "hello");
-  Value* string16_weak = original_dict.Set("string16", u"hello16");
-  Value* binary_weak = original_dict.Set("binary", Value::BlobStorage(42, '!'));
-
-  Value::List list;
-  list.Append(0);
-  list.Append(1);
-  Value* list_weak = original_dict.Set("list", std::move(list));
-
-  auto copy_dict = std::make_unique<Value>(original_dict.Clone());
-  auto copy_null = std::make_unique<Value>(null_weak->Clone());
-  auto copy_bool = std::make_unique<Value>(bool_weak->Clone());
-  auto copy_int = std::make_unique<Value>(int_weak->Clone());
-  auto copy_double = std::make_unique<Value>(double_weak->Clone());
-  auto copy_string = std::make_unique<Value>(string_weak->Clone());
-  auto copy_string16 = std::make_unique<Value>(string16_weak->Clone());
-  auto copy_binary = std::make_unique<Value>(binary_weak->Clone());
-  auto copy_list = std::make_unique<Value>(list_weak->Clone());
-
-  EXPECT_EQ(original_dict, *copy_dict);
-  EXPECT_EQ(*null_weak, *copy_null);
-  EXPECT_EQ(*bool_weak, *copy_bool);
-  EXPECT_EQ(*int_weak, *copy_int);
-  EXPECT_EQ(*double_weak, *copy_double);
-  EXPECT_EQ(*string_weak, *copy_string);
-  EXPECT_EQ(*string16_weak, *copy_string16);
-  EXPECT_EQ(*binary_weak, *copy_binary);
-  EXPECT_EQ(*list_weak, *copy_list);
-}
-
 TEST(ValuesTest, Merge) {
   Value::Dict base;
   base.Set("base_key", "base_key_value_base");
diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn
index c1136c49..048485d 100644
--- a/build/android/BUILD.gn
+++ b/build/android/BUILD.gn
@@ -155,12 +155,15 @@
 
 group("test_runner_device_support") {
   testonly = true
+
+  # We hardcode using these tools from the public sdk in devil_chromium.json and
+  # in pylib's constants.
   data = [
-    "${android_sdk_build_tools}/aapt",
-    "${android_sdk_build_tools}/dexdump",
-    "${android_sdk_build_tools}/lib64/libc++.so",
-    "${android_sdk_build_tools}/split-select",
-    "${android_sdk_root}/platform-tools/adb",
+    "${public_android_sdk_build_tools}/aapt",
+    "${public_android_sdk_build_tools}/dexdump",
+    "${public_android_sdk_build_tools}/lib64/libc++.so",
+    "${public_android_sdk_build_tools}/split-select",
+    "${public_android_sdk_root}/platform-tools/adb",
   ]
   data_deps = [
     ":apk_installer_data",
diff --git a/build/android/gyp/javac_output_processor.py b/build/android/gyp/javac_output_processor.py
index 6faf5de5..6f1fbf6 100755
--- a/build/android/gyp/javac_output_processor.py
+++ b/build/android/gyp/javac_output_processor.py
@@ -90,6 +90,9 @@
         re.compile(fileline_prefix + r'( error: symbol not found [\w.]+)$'),
     ]
 
+    self._filter_out_re = re.compile(r'.*warning.*Cannot use file \S+ because'
+                                     r' it is locked by another process')
+
     # Example: import org.chromium.url.GURL;
     self._import_re = re.compile(r'\s*import (?P<imported_class>[\w\.]+);$')
 
@@ -108,12 +111,14 @@
   def Process(self, lines):
     """ Processes javac output.
 
+      - Removes unnecessary output.
       - Applies colors to output.
       - Suggests GN dep to add for 'unresolved symbol in Java import' errors.
       """
     lines = self._ElaborateLinesForUnknownSymbol(iter(lines))
     for line in lines:
-      yield self._ApplyColors(line)
+      if not self._filter_out_re.match(line):
+        yield self._ApplyColors(line)
     if self._suggested_deps:
 
       def yellow(text):
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 5c1cd13..3414349 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -134,6 +134,7 @@
 ../gn_helpers.py
 ../print_python_deps.py
 ../skia_gold_common/__init__.py
+../skia_gold_common/output_managerless_skia_gold_session.py
 ../skia_gold_common/skia_gold_properties.py
 ../skia_gold_common/skia_gold_session.py
 ../skia_gold_common/skia_gold_session_manager.py
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index fb258c8..6ee83a4 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -435,6 +435,14 @@
   # if needed.
   default_executable_configs += [ "//build/config/win:cfi_linker" ]
 }
+if (is_fuchsia) {
+  # Sometimes executables are linked by rustc passing a command line to
+  # clang++. It includes "-pie" which is pointless on Fuchsia. Suppress the
+  # resulting (fatal) warning. Unfortunately there's no way to do this only
+  # for binaries linked by rustc; gn does not make the distinction.
+  default_executable_configs +=
+      [ "//build/config/fuchsia:rustc_no_pie_warning" ]
+}
 
 set_defaults("executable") {
   configs = default_executable_configs
@@ -457,6 +465,14 @@
   default_shared_library_configs +=
       [ "//build/config/android:hide_all_but_jni_onload" ]
 }
+if (is_fuchsia) {
+  # Sometimes shared libraries are linked by rustc passing a command line to
+  # clang++. It includes "-pie" which is pointless on Fuchsia. Suppress the
+  # resulting (fatal) warning. Unfortunately there's no way to do this only
+  # for binaries linked by rustc; gn does not make the distinction.
+  default_shared_library_configs +=
+      [ "//build/config/fuchsia:rustc_no_pie_warning" ]
+}
 set_defaults("shared_library") {
   configs = default_shared_library_configs
 }
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index fb272c0..d93bd13f 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1067,10 +1067,6 @@
         cflags += [ "--target=aarch64-linux-gnu" ]
         ldflags += [ "--target=aarch64-linux-gnu" ]
       }
-      if (is_high_end_android) {
-        cflags += [ "-march=armv8-a+lse" ]
-        ldflags += [ "-march=armv8-a+lse" ]
-      }
     } else if (current_cpu == "mipsel" && !is_nacl) {
       ldflags += [ "-Wl,--hash-style=sysv" ]
       if (custom_toolchain == "") {
diff --git a/build/config/fuchsia/BUILD.gn b/build/config/fuchsia/BUILD.gn
index 42f1b77a..dfc36ad 100644
--- a/build/config/fuchsia/BUILD.gn
+++ b/build/config/fuchsia/BUILD.gn
@@ -94,3 +94,10 @@
     ":sysroot_asan_runtime_libs",
   ]
 }
+
+# rustc gives the linker (clang++) "-pie" directives. clang++ complains on
+# Fuchsia that these don't make any sense. On Fuchsia alone, for Rust-linked
+# targets only, disable these warnings.
+config("rustc_no_pie_warning") {
+  ldflags = [ "-Wno-unused-command-line-argument" ]
+}
diff --git a/build/fuchsia/test/ffx_emulator.py b/build/fuchsia/test/ffx_emulator.py
index be473cc..0b1fbf95 100644
--- a/build/fuchsia/test/ffx_emulator.py
+++ b/build/fuchsia/test/ffx_emulator.py
@@ -68,6 +68,8 @@
                 ('-l', os.path.join(self._logs_dir, 'emulator_log')))
         if self._with_network:
             emu_command.extend(('--net', 'tap'))
+        else:
+            emu_command.extend(('--net', 'user'))
 
         # TODO(https://crbug.com/1336776): remove when ffx has native support
         # for starting emulator on arm64 host.
@@ -119,9 +121,8 @@
                 configs = ['emu.start.timeout=90']
                 if i > 0:
                     logging.warning(
-                        'Emulator failed to start. Turning on debug')
-                    configs.append('log.level=debug')
-                run_ffx_command(emu_command, timeout=85, configs=configs)
+                        'Emulator failed to start.')
+                run_ffx_command(emu_command, timeout=100, configs=configs)
                 break
             except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
                 run_ffx_command(('emu', 'stop'))
diff --git a/build/fuchsia/test/run_test.py b/build/fuchsia/test/run_test.py
index d9b929b..1fa78fc 100755
--- a/build/fuchsia/test/run_test.py
+++ b/build/fuchsia/test/run_test.py
@@ -95,6 +95,7 @@
             stack.enter_context(ScopedFfxConfig('daemon.autostart', 'false'))
             stack.enter_context(
                 ScopedFfxConfig('discovery.zedboot.enabled', 'true'))
+            stack.enter_context(ScopedFfxConfig('log.level', 'debug'))
             log_manager = stack.enter_context(LogManager(runner_args.logs_dir))
             start_ffx_daemon()
             stack.callback(stop_ffx_daemon)
diff --git a/build/skia_gold_common/skia_gold_properties.py b/build/skia_gold_common/skia_gold_properties.py
index 68a175f..6ff3742 100644
--- a/build/skia_gold_common/skia_gold_properties.py
+++ b/build/skia_gold_common/skia_gold_properties.py
@@ -29,7 +29,10 @@
 
 class SkiaGoldProperties():
   def __init__(self, args: ParsedCmdArgs):
-    """Abstract class to validate and store properties related to Skia Gold.
+    """Class to validate and store properties related to Skia Gold.
+
+    The base implementation is usable on its own, but is meant to be overridden
+    as necessary.
 
     Args:
       args: The parsed arguments from an argparse.ArgumentParser.
diff --git a/build/skia_gold_common/skia_gold_session_manager.py b/build/skia_gold_common/skia_gold_session_manager.py
index 976a72e..300ddf89 100644
--- a/build/skia_gold_common/skia_gold_session_manager.py
+++ b/build/skia_gold_common/skia_gold_session_manager.py
@@ -7,6 +7,7 @@
 import tempfile
 from typing import Optional, Type, Union
 
+from skia_gold_common import output_managerless_skia_gold_session
 from skia_gold_common import skia_gold_properties
 from skia_gold_common import skia_gold_session
 
@@ -16,11 +17,14 @@
 class SkiaGoldSessionManager():
   def __init__(self, working_dir: str,
                gold_properties: skia_gold_properties.SkiaGoldProperties):
-    """Abstract class to manage one or more skia_gold_session.SkiaGoldSessions.
+    """Class to manage one or more skia_gold_session.SkiaGoldSessions.
 
     A separate session is required for each instance/corpus/keys_file
     combination, so this class will lazily create them as necessary.
 
+    The base implementation is usable on its own, but is meant to be overridden
+    as necessary.
+
     Args:
       working_dir: The working directory under which each individual
           SkiaGoldSessions' working directory will be created.
@@ -86,7 +90,7 @@
     Returns:
       A reference to a SkiaGoldSession class.
     """
-    raise NotImplementedError
+    return output_managerless_skia_gold_session.OutputManagerlessSkiaGoldSession
 
 
 def _GetKeysAsDict(keys_input: KeysInputType) -> dict:
diff --git a/buildtools/reclient_cfgs/fetch_reclient_cfgs.py b/buildtools/reclient_cfgs/fetch_reclient_cfgs.py
index 98c8290..2cac273f 100755
--- a/buildtools/reclient_cfgs/fetch_reclient_cfgs.py
+++ b/buildtools/reclient_cfgs/fetch_reclient_cfgs.py
@@ -78,14 +78,18 @@
         return False
     with open(tmpl_path) as f:
       reproxy_cfg_tmpl = string.Template(REPROXY_CFG_HEADER+f.read())
-    scandeps_bin_name = 'scandeps_server'
+    depsscanner_address = 'exec://' + os.path.join(CHROMIUM_SRC,
+                                                   'buildtools',
+                                                   'reclient',
+                                                   'scandeps_server')
     if sys.platform.startswith('win'):
-       scandeps_bin_name += ".exe"
+       # TODO(b/278871789) re-enable when reproxy version is > 105
+       # depsscanner_address += ".exe"
+       depsscanner_address = ""
     reproxy_cfg = reproxy_cfg_tmpl.substitute({
       'rbe_instance': rbe_instance,
       'reproxy_cfg_template': reproxy_cfg_template,
-      'scandeps_bin_path':
-        os.path.join(CHROMIUM_SRC, 'buildtools', 'reclient', scandeps_bin_name),
+      'depsscanner_address': depsscanner_address,
     })
     reproxy_cfg_path = os.path.join(THIS_DIR, 'reproxy.cfg')
     with open(reproxy_cfg_path, 'w') as f:
diff --git a/buildtools/reclient_cfgs/reproxy_cfg_templates/reproxy.cfg.template b/buildtools/reclient_cfgs/reproxy_cfg_templates/reproxy.cfg.template
index 6e60f359..bd889f0 100644
--- a/buildtools/reclient_cfgs/reproxy_cfg_templates/reproxy.cfg.template
+++ b/buildtools/reclient_cfgs/reproxy_cfg_templates/reproxy.cfg.template
@@ -9,7 +9,7 @@
 # enable_deps_cache=true
 use_unified_uploads=true
 fast_log_collection=true
-depsscanner_address=exec://${scandeps_bin_path}
+depsscanner_address=$depsscanner_address
 
 # Improve upload/download concurrency
 max_concurrent_streams_per_conn=50
diff --git a/cc/input/input_handler.h b/cc/input/input_handler.h
index 8832e2b..af0e9c1 100644
--- a/cc/input/input_handler.h
+++ b/cc/input/input_handler.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "base/time/time.h"
diff --git a/cc/paint/filter_operations.cc b/cc/paint/filter_operations.cc
index 6cf836b..d120405 100644
--- a/cc/paint/filter_operations.cc
+++ b/cc/paint/filter_operations.cc
@@ -15,6 +15,7 @@
 #include "base/values.h"
 #include "cc/paint/filter_operation.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
 
 namespace cc {
 
@@ -107,6 +108,13 @@
   return false;
 }
 
+gfx::Rect FilterOperations::ExpandRectForPixelMovement(
+    const gfx::Rect& rect) const {
+  gfx::RectF expanded_rect(rect);
+  expanded_rect.Outset(MaximumPixelMovement());
+  return gfx::ToEnclosingRect(expanded_rect);
+}
+
 float FilterOperations::MaximumPixelMovement() const {
   float max_movement = 0.;
   for (size_t i = 0; i < operations_.size(); ++i) {
diff --git a/cc/paint/filter_operations.h b/cc/paint/filter_operations.h
index 8c947b8c..2650189 100644
--- a/cc/paint/filter_operations.h
+++ b/cc/paint/filter_operations.h
@@ -62,7 +62,11 @@
   gfx::Rect MapRectReverse(const gfx::Rect& rect, const SkMatrix& matrix) const;
 
   bool HasFilterThatMovesPixels() const;
-  float MaximumPixelMovement() const;
+
+  // Expands `rect` to add any additional area that applying pixel moving
+  // filters will modify.
+  gfx::Rect ExpandRectForPixelMovement(const gfx::Rect& rect) const;
+
   bool HasFilterThatAffectsOpacity() const;
   bool HasReferenceFilter() const;
   bool HasFilterOfType(FilterOperation::FilterType type) const;
@@ -92,6 +96,8 @@
   std::string ToString() const;
 
  private:
+  float MaximumPixelMovement() const;
+
   std::vector<FilterOperation> operations_;
 };
 
diff --git a/cc/paint/filter_operations_unittest.cc b/cc/paint/filter_operations_unittest.cc
index f3bb045..ae410a2 100644
--- a/cc/paint/filter_operations_unittest.cc
+++ b/cc/paint/filter_operations_unittest.cc
@@ -984,32 +984,38 @@
   EXPECT_FALSE(filters.HasFilterOfType(FilterOperation::ZOOM));
 }
 
-TEST(FilterOperationsTest, MaximumPixelMovement) {
+TEST(FilterOperationsTest, ExpandRectForPixelMovement) {
+  constexpr gfx::Rect test_rect(0, 0, 100, 100);
   FilterOperations filters;
 
   filters.Append(FilterOperation::CreateBlurFilter(20));
-  EXPECT_FLOAT_EQ(20.f * 3, filters.MaximumPixelMovement());
+  EXPECT_EQ(gfx::Rect(-60, -60, 220, 220),
+            filters.ExpandRectForPixelMovement(test_rect));
 
   filters.Clear();
   filters.Append(FilterOperation::CreateDropShadowFilter(
       gfx::Point(3, -8), 20, SkColors::kTransparent));
-  float max_movement = fmax(std::abs(3), std::abs(-8)) + 20.f * 3;
-  EXPECT_FLOAT_EQ(max_movement, filters.MaximumPixelMovement());
+  // max_movement = max(std::abs(3), std::abs(-8)) + 20 * 3;
+  EXPECT_EQ(gfx::Rect(-68, -68, 236, 236),
+            filters.ExpandRectForPixelMovement(test_rect));
 
   filters.Clear();
   filters.Append(FilterOperation::CreateZoomFilter(2, 3));
   // max movement = zoom_inset = 3
-  EXPECT_FLOAT_EQ(3.f, filters.MaximumPixelMovement());
+  EXPECT_EQ(gfx::Rect(-3, -3, 106, 106),
+            filters.ExpandRectForPixelMovement(test_rect));
 
   filters.Clear();
   filters.Append(FilterOperation::CreateOffsetFilter(gfx::Point(3, -4)));
-  EXPECT_FLOAT_EQ(4.0f, filters.MaximumPixelMovement());
+  EXPECT_EQ(gfx::Rect(-4, -4, 108, 108),
+            filters.ExpandRectForPixelMovement(test_rect));
 
   filters.Clear();
   filters.Append(FilterOperation::CreateReferenceFilter(
       sk_make_sp<OffsetPaintFilter>(10, 10, nullptr)));
   // max movement = 100.
-  EXPECT_FLOAT_EQ(100.f, filters.MaximumPixelMovement());
+  EXPECT_EQ(gfx::Rect(-100, -100, 300, 300),
+            filters.ExpandRectForPixelMovement(test_rect));
 
   // For filters that don't move pixels. HasFilterThatMovesPixels() = false.
   filters.Clear();
@@ -1026,7 +1032,7 @@
   filters.Append(FilterOperation::CreateContrastFilter(3.f));
   filters.Append(FilterOperation::CreateSaturatingBrightnessFilter(7.f));
 
-  EXPECT_FLOAT_EQ(0.f, filters.MaximumPixelMovement());
+  EXPECT_EQ(test_rect, filters.ExpandRectForPixelMovement(test_rect));
 }
 
 }  // namespace
diff --git a/cc/slim/layer_tree.h b/cc/slim/layer_tree.h
index e63ffa2..7127834 100644
--- a/cc/slim/layer_tree.h
+++ b/cc/slim/layer_tree.h
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/component_export.h"
+#include "base/containers/flat_map.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
@@ -27,6 +28,8 @@
 
 namespace viz {
 class CopyOutputRequest;
+class SurfaceId;
+class SurfaceRange;
 }
 
 namespace cc::slim {
@@ -39,6 +42,19 @@
 // submitting frames and managing the lifetime of `FrameSink`s.
 class COMPONENT_EXPORT(CC_SLIM) LayerTree {
  public:
+  using SurfaceRangesAndCounts = base::flat_map<viz::SurfaceRange, int>;
+
+  // A scoped object to keep a `viz::Surface` referenced, such that a
+  // `CopyOutputRequest` can be made against it, even after the original
+  // `SurfaceLayer` is destroyed.
+  class ScopedKeepSurfaceAlive {
+   public:
+    ScopedKeepSurfaceAlive() = default;
+    ScopedKeepSurfaceAlive(const ScopedKeepSurfaceAlive&) = delete;
+    ScopedKeepSurfaceAlive& operator=(const ScopedKeepSurfaceAlive&) = delete;
+    virtual ~ScopedKeepSurfaceAlive() = default;
+  };
+
   struct COMPONENT_EXPORT(CC_SLIM) InitParams {
     InitParams();
     ~InitParams();
@@ -146,6 +162,12 @@
 
   // Set the top controls visual height for the next frame submitted.
   virtual void UpdateTopControlsVisibleHeight(float height) = 0;
+
+  virtual std::unique_ptr<ScopedKeepSurfaceAlive> CreateScopedKeepSurfaceAlive(
+      const viz::SurfaceId& surface_id) = 0;
+
+  // Exposes the ranges of referenced surfaces for testing.
+  virtual const SurfaceRangesAndCounts& GetSurfaceRangesForTesting() const = 0;
 };
 
 }  // namespace cc::slim
diff --git a/cc/slim/layer_tree_cc_wrapper.cc b/cc/slim/layer_tree_cc_wrapper.cc
index 46a1f77..f29cfa0 100644
--- a/cc/slim/layer_tree_cc_wrapper.cc
+++ b/cc/slim/layer_tree_cc_wrapper.cc
@@ -46,6 +46,27 @@
   const float height_;
 };
 
+class LayerTreeCcWrapperScopedKeepSurfaceAlive
+    : public LayerTree::ScopedKeepSurfaceAlive {
+ public:
+  LayerTreeCcWrapperScopedKeepSurfaceAlive(
+      base::WeakPtr<LayerTreeCcWrapper> layer_tree,
+      const viz::SurfaceId& surface_id)
+      : layer_tree_(std::move(layer_tree)), range_(surface_id, surface_id) {
+    layer_tree_->AddSurfaceRange(range_);
+  }
+
+  ~LayerTreeCcWrapperScopedKeepSurfaceAlive() override {
+    if (layer_tree_) {
+      layer_tree_->RemoveSurfaceRange(range_);
+    }
+  }
+
+ private:
+  const base::WeakPtr<LayerTreeCcWrapper> layer_tree_;
+  const viz::SurfaceRange range_;
+};
+
 }  // namespace
 
 LayerTreeCcWrapper::LayerTreeCcWrapper(InitParams init_params)
@@ -170,6 +191,19 @@
   host_->ReleaseLayerTreeFrameSink();
 }
 
+std::unique_ptr<LayerTree::ScopedKeepSurfaceAlive>
+LayerTreeCcWrapper::CreateScopedKeepSurfaceAlive(
+    const viz::SurfaceId& surface_id) {
+  return std::make_unique<LayerTreeCcWrapperScopedKeepSurfaceAlive>(
+      weak_factory_.GetWeakPtr(), surface_id);
+}
+
+const LayerTree::SurfaceRangesAndCounts&
+LayerTreeCcWrapper::GetSurfaceRangesForTesting() const {
+  const auto* const_host = host_.get();
+  return const_host->pending_commit_state()->surface_ranges;
+}
+
 void LayerTreeCcWrapper::BeginMainFrame(const viz::BeginFrameArgs& args) {
   client_->BeginFrame(args);
 }
@@ -198,6 +232,16 @@
   client_->DidLoseLayerTreeFrameSink();
 }
 
+void LayerTreeCcWrapper::AddSurfaceRange(
+    const viz::SurfaceRange& surface_range) {
+  host_->AddSurfaceRange(surface_range);
+}
+
+void LayerTreeCcWrapper::RemoveSurfaceRange(
+    const viz::SurfaceRange& surface_range) {
+  host_->RemoveSurfaceRange(surface_range);
+}
+
 std::unique_ptr<cc::BeginMainFrameMetrics>
 LayerTreeCcWrapper::GetBeginMainFrameMetrics() {
   return nullptr;
diff --git a/cc/slim/layer_tree_cc_wrapper.h b/cc/slim/layer_tree_cc_wrapper.h
index 75b495e..2386881 100644
--- a/cc/slim/layer_tree_cc_wrapper.h
+++ b/cc/slim/layer_tree_cc_wrapper.h
@@ -53,6 +53,9 @@
   void SetRoot(scoped_refptr<Layer> root) override;
   void SetFrameSink(std::unique_ptr<FrameSink> sink) override;
   void ReleaseLayerTreeFrameSink() override;
+  std::unique_ptr<ScopedKeepSurfaceAlive> CreateScopedKeepSurfaceAlive(
+      const viz::SurfaceId& surface_id) override;
+  const SurfaceRangesAndCounts& GetSurfaceRangesForTesting() const override;
 
   // cc::LayerTreeHostClient.
   void WillBeginMainFrame() override {}
@@ -101,6 +104,11 @@
   void DidSubmitCompositorFrame() override;
   void DidLoseLayerTreeFrameSink() override;
 
+  // Called by `LayerTree::ScopedKeepSurfaceAlive`'s to keep the surface
+  // referenced.
+  void AddSurfaceRange(const viz::SurfaceRange& surface_range);
+  void RemoveSurfaceRange(const viz::SurfaceRange& surface_range);
+
  private:
   friend LayerTree;
   explicit LayerTreeCcWrapper(InitParams init_params);
@@ -110,6 +118,8 @@
   std::unique_ptr<cc::LayerTreeHost> host_;
 
   scoped_refptr<Layer> root_;
+
+  base::WeakPtrFactory<LayerTreeCcWrapper> weak_factory_{this};
 };
 
 }  // namespace cc::slim
diff --git a/cc/slim/layer_tree_impl.cc b/cc/slim/layer_tree_impl.cc
index ceda196..5c21968d 100644
--- a/cc/slim/layer_tree_impl.cc
+++ b/cc/slim/layer_tree_impl.cc
@@ -37,6 +37,30 @@
 
 namespace cc::slim {
 
+namespace {
+
+class LayerTreeImplScopedKeepSurfaceAlive
+    : public LayerTree::ScopedKeepSurfaceAlive {
+ public:
+  LayerTreeImplScopedKeepSurfaceAlive(base::WeakPtr<LayerTreeImpl> layer_tree,
+                                      const viz::SurfaceId& surface_id)
+      : layer_tree_(std::move(layer_tree)), range_(surface_id, surface_id) {
+    layer_tree_->AddSurfaceRange(range_);
+  }
+
+  ~LayerTreeImplScopedKeepSurfaceAlive() override {
+    if (layer_tree_) {
+      layer_tree_->RemoveSurfaceRange(range_);
+    }
+  }
+
+ private:
+  const base::WeakPtr<LayerTreeImpl> layer_tree_;
+  const viz::SurfaceRange range_;
+};
+
+}  // namespace
+
 LayerTreeImpl::PresentationCallbackInfo::PresentationCallbackInfo(
     uint32_t frame_token,
     std::vector<PresentationCallback> presentation_callbacks,
@@ -227,6 +251,17 @@
   damage_from_previous_frame_.clear();
 }
 
+std::unique_ptr<LayerTree::ScopedKeepSurfaceAlive>
+LayerTreeImpl::CreateScopedKeepSurfaceAlive(const viz::SurfaceId& surface_id) {
+  return std::make_unique<LayerTreeImplScopedKeepSurfaceAlive>(
+      weak_factory_.GetWeakPtr(), surface_id);
+}
+
+const LayerTree::SurfaceRangesAndCounts&
+LayerTreeImpl::GetSurfaceRangesForTesting() const {
+  return referenced_surfaces_;
+}
+
 bool LayerTreeImpl::BeginFrame(
     const viz::BeginFrameArgs& args,
     viz::CompositorFrame& out_frame,
@@ -343,14 +378,21 @@
 
 void LayerTreeImpl::AddSurfaceRange(const viz::SurfaceRange& range) {
   DCHECK(range.IsValid());
-  DCHECK(!referenced_surfaces_.contains(range));
-  referenced_surfaces_.insert(range);
+  DCHECK(!referenced_surfaces_.contains(range) ||
+         referenced_surfaces_[range] >= 1);
+  if (++(referenced_surfaces_[range]) == 1) {
+    SetNeedsDraw();
+  }
 }
 
 void LayerTreeImpl::RemoveSurfaceRange(const viz::SurfaceRange& range) {
   DCHECK(range.IsValid());
-  DCHECK(referenced_surfaces_.contains(range));
-  referenced_surfaces_.erase(range);
+  DCHECK(referenced_surfaces_.contains(range) &&
+         referenced_surfaces_[range] >= 1);
+  if (--(referenced_surfaces_[range]) == 0) {
+    referenced_surfaces_.erase(range);
+    SetNeedsDraw();
+  }
 }
 
 void LayerTreeImpl::MaybeRequestFrameSink() {
@@ -432,8 +474,10 @@
       viz::BeginFrameAck(args, /*has_damage=*/true);
   out_frame.metadata.device_scale_factor = device_scale_factor_;
   out_frame.metadata.root_background_color = background_color_;
-  out_frame.metadata.referenced_surfaces = std::vector<viz::SurfaceRange>(
-      referenced_surfaces_.begin(), referenced_surfaces_.end());
+  out_frame.metadata.referenced_surfaces.reserve(referenced_surfaces_.size());
+  for (const auto& [range, range_counts] : referenced_surfaces_) {
+    out_frame.metadata.referenced_surfaces.emplace_back(range);
+  }
   out_frame.metadata.top_controls_visible_height = top_controls_visible_height_;
   top_controls_visible_height_.reset();
   out_frame.metadata.display_transform_hint = display_transform_hint_;
diff --git a/cc/slim/layer_tree_impl.h b/cc/slim/layer_tree_impl.h
index 4e710b6..d04aab7 100644
--- a/cc/slim/layer_tree_impl.h
+++ b/cc/slim/layer_tree_impl.h
@@ -13,7 +13,6 @@
 
 #include "base/component_export.h"
 #include "base/containers/circular_deque.h"
-#include "base/containers/flat_set.h"
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
@@ -78,6 +77,9 @@
   void SetRoot(scoped_refptr<Layer> root) override;
   void SetFrameSink(std::unique_ptr<FrameSink> sink) override;
   void ReleaseLayerTreeFrameSink() override;
+  std::unique_ptr<ScopedKeepSurfaceAlive> CreateScopedKeepSurfaceAlive(
+      const viz::SurfaceId& surface_id) override;
+  const SurfaceRangesAndCounts& GetSurfaceRangesForTesting() const override;
 
   // FrameSinkImplClient.
   bool BeginFrame(const viz::BeginFrameArgs& args,
@@ -210,7 +212,7 @@
   float device_scale_factor_ = 1.0f;
   SkColor4f background_color_ = SkColors::kWhite;
   absl::optional<float> top_controls_visible_height_;
-  base::flat_set<viz::SurfaceRange> referenced_surfaces_;
+  SurfaceRangesAndCounts referenced_surfaces_;
   viz::FrameTokenGenerator next_frame_token_;
   gfx::OverlayTransform display_transform_hint_ = gfx::OVERLAY_TRANSFORM_NONE;
 
diff --git a/cc/slim/slim_layer_tree_compositor_frame_unittest.cc b/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
index 31fb211..1b4a251 100644
--- a/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
+++ b/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
@@ -31,6 +31,7 @@
 #include "components/viz/common/quads/texture_draw_quad.h"
 #include "components/viz/common/resources/transferable_resource.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
 #include "components/viz/test/draw_quad_matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -2118,6 +2119,59 @@
   }
 }
 
+// Testing that {Add|Remove}SurfaceRange should trigger a draw via
+// `SetNeedsDraw`, where the added or removed surface range should be reflected
+// in the metadata of the next frame's metadata.
+TEST_F(SlimLayerTreeCompositorFrameTest,
+       AddRemoveSurfaceRangesTriggerSetNeedsDraw) {
+  auto surface_layer = SurfaceLayer::Create();
+  surface_layer->SetBounds(viewport_.size());
+  surface_layer->SetIsDrawable(true);
+  surface_layer->SetContentsOpaque(true);
+  layer_tree_->SetRoot(surface_layer);
+
+  base::UnguessableToken token = base::UnguessableToken::Create();
+  viz::SurfaceId start(viz::FrameSinkId(1u, 2u),
+                       viz::LocalSurfaceId(3u, 4u, token));
+  viz::SurfaceId end(viz::FrameSinkId(1u, 2u),
+                     viz::LocalSurfaceId(5u, 6u, token));
+  cc::DeadlinePolicy deadline_policy = cc::DeadlinePolicy::UseDefaultDeadline();
+  surface_layer->SetOldestAcceptableFallback(start);
+  surface_layer->SetSurfaceId(end, deadline_policy);
+
+  // Add/remove a SurfaceRange different from the one of the `surface_layer`.
+  {
+    layer_tree_->AddSurfaceRange(viz::SurfaceRange(end, end));
+    const viz::CompositorFrame frame = ProduceFrame();
+    EXPECT_THAT(frame.metadata.referenced_surfaces,
+                testing::UnorderedElementsAre(viz::SurfaceRange(start, end),
+                                              viz::SurfaceRange(end, end)));
+  }
+  {
+    layer_tree_->RemoveSurfaceRange(viz::SurfaceRange(end, end));
+    const viz::CompositorFrame frame = ProduceFrame();
+    EXPECT_THAT(frame.metadata.referenced_surfaces,
+                testing::UnorderedElementsAre(viz::SurfaceRange(start, end)));
+  }
+
+  // Add/remove a SurfaceRange that's the same as the one of the
+  // `surface_layer`. Since the ranges are the same, only one range entry is
+  // referenced in the metadata.
+  {
+    layer_tree_->AddSurfaceRange(viz::SurfaceRange(start, end));
+
+    const viz::CompositorFrame frame = ProduceFrame();
+    EXPECT_THAT(frame.metadata.referenced_surfaces,
+                testing::UnorderedElementsAre(viz::SurfaceRange(start, end)));
+  }
+  {
+    layer_tree_->RemoveSurfaceRange(viz::SurfaceRange(start, end));
+    const viz::CompositorFrame frame = ProduceFrame();
+    EXPECT_THAT(frame.metadata.referenced_surfaces,
+                testing::UnorderedElementsAre(viz::SurfaceRange(start, end)));
+  }
+}
+
 }  // namespace
 
 }  // namespace cc::slim
diff --git a/cc/slim/slim_layer_tree_unittest.cc b/cc/slim/slim_layer_tree_unittest.cc
index 4785459..97da4084 100644
--- a/cc/slim/slim_layer_tree_unittest.cc
+++ b/cc/slim/slim_layer_tree_unittest.cc
@@ -19,6 +19,7 @@
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "components/viz/common/surfaces/surface_range.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/overlay_transform.h"
@@ -335,18 +336,19 @@
   layer->SetSurfaceId(end, cc::DeadlinePolicy::UseDefaultDeadline());
 
   layer_tree_->SetRoot(layer);
-  EXPECT_EQ(layer_tree_->referenced_surfaces(),
-            base::flat_set<viz::SurfaceRange>{viz::SurfaceRange(start, end)});
+  EXPECT_THAT(layer_tree_->referenced_surfaces(),
+              testing::UnorderedElementsAre(
+                  std::make_pair(viz::SurfaceRange(start, end), 1)));
 
   viz::SurfaceId new_end(viz::FrameSinkId(1u, 2u),
                          viz::LocalSurfaceId(7u, 8u, token));
   layer->SetSurfaceId(new_end, cc::DeadlinePolicy::UseDefaultDeadline());
-  EXPECT_EQ(layer_tree_->referenced_surfaces(),
-            std::vector<viz::SurfaceRange>{viz::SurfaceRange(start, new_end)});
+  EXPECT_THAT(layer_tree_->referenced_surfaces(),
+              testing::UnorderedElementsAre(
+                  std::make_pair(viz::SurfaceRange(start, new_end), 1)));
 
   layer_tree_->SetRoot(nullptr);
-  EXPECT_EQ(layer_tree_->referenced_surfaces(),
-            std::vector<viz::SurfaceRange>());
+  EXPECT_TRUE(layer_tree_->referenced_surfaces().empty());
 }
 
 TEST_F(SlimLayerTreeTest, DestroyTreeBeforeLayer) {
diff --git a/cc/slim/test_layer_tree_impl.h b/cc/slim/test_layer_tree_impl.h
index c1c57dd3..0d6d3a22 100644
--- a/cc/slim/test_layer_tree_impl.h
+++ b/cc/slim/test_layer_tree_impl.h
@@ -5,7 +5,6 @@
 #ifndef CC_SLIM_TEST_LAYER_TREE_IMPL_H_
 #define CC_SLIM_TEST_LAYER_TREE_IMPL_H_
 
-#include "base/containers/flat_set.h"
 #include "cc/slim/layer_tree_impl.h"
 #include "cc/slim/test_layer_tree_client.h"
 
@@ -25,7 +24,7 @@
   ~TestLayerTreeImpl() override = default;
 
   using LayerTreeImpl::NeedsBeginFrames;
-  const base::flat_set<viz::SurfaceRange>& referenced_surfaces() const {
+  const SurfaceRangesAndCounts& referenced_surfaces() const {
     return referenced_surfaces_;
   }
 
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 1f00bc2eb..cbd4b51 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -1645,8 +1645,10 @@
 }
 
 void LayerTreeHost::AddSurfaceRange(const viz::SurfaceRange& surface_range) {
-  if (++pending_commit_state()->surface_ranges[surface_range] == 1)
+  if (++pending_commit_state()->surface_ranges[surface_range] == 1) {
     pending_commit_state()->needs_surface_ranges_sync = true;
+    SetNeedsCommit();
+  }
 }
 
 void LayerTreeHost::RemoveSurfaceRange(const viz::SurfaceRange& surface_range) {
@@ -1657,6 +1659,7 @@
   if (--iter->second <= 0) {
     pending_commit_state()->surface_ranges.erase(iter);
     pending_commit_state()->needs_surface_ranges_sync = true;
+    SetNeedsCommit();
   }
 }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 62bd933..0fda172 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=115
 MINOR=0
-BUILD=5766
+BUILD=5767
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index e195a2da..020d271 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2622,6 +2622,7 @@
       "//chrome/browser/optimization_guide/android:unit_device_javatests",
       "//chrome/browser/partnercustomizations:unit_device_javatests",
       "//chrome/browser/password_edit_dialog/android:unit_device_javatests",
+      "//chrome/browser/recent_tabs/internal:unit_device_javatests",
       "//chrome/browser/signin/services/android:unit_device_javatests",
       "//chrome/browser/thumbnail/generator:unit_device_javatests",
       "//chrome/browser/ui/android/appmenu/internal:unit_device_javatests",
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index e6e5538..b775959 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -356,6 +356,7 @@
   "java/res/drawable/bg_circle_new_tab_button_folio.xml",
   "java/res/drawable/bg_tabstrip_tab_divider.xml",
   "java/res/drawable/bg_white_dialog.xml",
+  "java/res/drawable/bookmark_empty_state_illustration.xml",
   "java/res/drawable/bookmark_save_flow_ripple.xml",
   "java/res/drawable/bookmark_title_bar_shadow.xml",
   "java/res/drawable/bookmark_widget_list_selector.xml",
@@ -435,6 +436,7 @@
   "java/res/drawable/price_tracking_enabled_filled.xml",
   "java/res/drawable/price_tracking_enabled_outline.xml",
   "java/res/drawable/qr_code.xml",
+  "java/res/drawable/reading_list_empty_state_illustration.xml",
   "java/res/drawable/safety_check.xml",
   "java/res/drawable/screenshot.xml",
   "java/res/drawable/section_tab_background.xml",
diff --git a/chrome/android/features/cablev2_authenticator/BUILD.gn b/chrome/android/features/cablev2_authenticator/BUILD.gn
index ced6c95..fcabdd72 100644
--- a/chrome/android/features/cablev2_authenticator/BUILD.gn
+++ b/chrome/android/features/cablev2_authenticator/BUILD.gn
@@ -24,7 +24,9 @@
     "//components/webauthn/android:java",
     "//content/public/android:content_java",
     "//mojo/public/mojom/base:base_java",
+    "//services/device/public/java:device_feature_list_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_core_core_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
     "//third_party/androidx:androidx_vectordrawable_vectordrawable_animated_java",
     "//third_party/blink/public/mojom:android_mojo_bindings_java",
diff --git a/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticatorUI.java b/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticatorUI.java
index f18de302..d157168 100644
--- a/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticatorUI.java
+++ b/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticatorUI.java
@@ -27,6 +27,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.RequiresApi;
+import androidx.core.app.NotificationManagerCompat;
 import androidx.fragment.app.Fragment;
 import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
 import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
@@ -36,6 +37,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.device.DeviceFeatureList;
 import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate;
 import org.chromium.ui.permissions.AndroidPermissionDelegate;
 import org.chromium.ui.widget.Toast;
@@ -241,8 +243,15 @@
                 case QR_CONFIRM:
                     if (event == Event.QR_ALLOW_BUTTON_CLICKED) {
                         ViewGroup top = (ViewGroup) getView();
-                        mAuthenticator.setQRLinking(
-                                ((CheckBox) top.findViewById(R.id.qr_link)).isChecked());
+                        boolean link = ((CheckBox) top.findViewById(R.id.qr_link)).isChecked();
+                        if (link
+                                && !DeviceFeatureList.isEnabled(
+                                        DeviceFeatureList
+                                                .WEBAUTHN_HYBRID_LINK_WITHOUT_NOTIFICATIONS)) {
+                            link = NotificationManagerCompat.from(getContext())
+                                           .areNotificationsEnabled();
+                        }
+                        mAuthenticator.setQRLinking(link);
                         mState = State.CHECK_SCREENLOCK;
                         break;
                     } else if (event == Event.QR_DENY_BUTTON_CLICKED) {
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java
index 1a93d15e..f0a2e6e 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java
@@ -6,7 +6,6 @@
 
 import static org.chromium.base.ThreadUtils.assertOnUiThread;
 
-import android.app.Activity;
 import android.util.SparseArray;
 
 import androidx.annotation.VisibleForTesting;
@@ -27,10 +26,12 @@
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
 
+import java.util.HashMap;
+
 class ManualFillingComponentBridge {
     private final SparseArray<PropertyProvider<AccessorySheetData>> mProviders =
             new SparseArray<>();
-    private PropertyProvider<Action[]> mActionProvider;
+    private HashMap<Integer, PropertyProvider<Action[]>> mActionProviders = new HashMap<>();
     private final WindowAndroid mWindowAndroid;
     private final WebContents mWebContents;
     private long mNativeView;
@@ -74,36 +75,9 @@
     }
 
     @CalledByNative
-    private void onAutomaticGenerationStatusChanged(boolean available) {
-        final Action[] generationAction;
-        final Activity activity = mWindowAndroid.getActivity().get();
-        if (available && activity != null) {
-            // This is meant to suppress the warning that the short string is not used.
-            // TODO(crbug.com/855581): Switch between strings based on whether they fit on the
-            // screen or not.
-            boolean useLongString = true;
-            String caption = useLongString
-                    ? activity.getString(R.string.password_generation_accessory_button)
-                    : activity.getString(R.string.password_generation_accessory_button_short);
-            generationAction = new Action[] {
-                    new Action(caption, AccessoryAction.GENERATE_PASSWORD_AUTOMATIC, (action) -> {
-                        assert mNativeView
-                                != 0
-                            : "Controller has been destroyed but the bridge wasn't cleaned up!";
-                        ManualFillingMetricsRecorder.recordActionSelected(
-                                AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
-                        ManualFillingComponentBridgeJni.get().onOptionSelected(mNativeView,
-                                ManualFillingComponentBridge.this,
-                                AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
-                    })};
-        } else {
-            generationAction = new Action[0];
-        }
-        if (mActionProvider == null && getManualFillingComponent() != null) {
-            mActionProvider = new PropertyProvider<>(AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
-            getManualFillingComponent().registerActionProvider(mWebContents, mActionProvider);
-        }
-        if (mActionProvider != null) mActionProvider.notifyObservers(generationAction);
+    private void onAccessoryActionAvailabilityChanged(
+            boolean available, @AccessoryAction int actionType) {
+        createOrClearAction(available, actionType);
     }
 
     @CalledByNative
@@ -280,17 +254,67 @@
     }
 
     private void onComponentDestroyed() {
-        if (mNativeView != 0) {
-            ManualFillingComponentBridgeJni.get().onViewDestroyed(
-                    mNativeView, ManualFillingComponentBridge.this);
-        }
+        if (mNativeView == 0) return; // Component was destroyed already.
+        ManualFillingComponentBridgeJni.get().onViewDestroyed(
+                mNativeView, ManualFillingComponentBridge.this);
     }
 
     private void requestSheet(int sheetType) {
-        if (mNativeView != 0) {
-            ManualFillingComponentBridgeJni.get().requestAccessorySheet(
-                    mNativeView, ManualFillingComponentBridge.this, sheetType);
+        if (mNativeView == 0) return; // Component was destroyed already.
+        ManualFillingComponentBridgeJni.get().requestAccessorySheet(
+                mNativeView, ManualFillingComponentBridge.this, sheetType);
+    }
+
+    private void createOrClearAction(boolean available, @AccessoryAction int actionType) {
+        if (getManualFillingComponent() == null) return; // Actions are not displayed.
+        final Action[] actions = available ? createSingleAction(actionType) : new Action[0];
+        getOrCreateActionProvider(actionType).notifyObservers(actions);
+    }
+
+    private Action[] createSingleAction(@AccessoryAction int actionType) {
+        return new Action[] {
+                new Action(getActionTitle(actionType), actionType, this::onActionSelected)};
+    }
+
+    private PropertyProvider<Action[]> getOrCreateActionProvider(@AccessoryAction int actionType) {
+        assert getManualFillingComponent()
+                != null : "Bridge has been destroyed but the bridge wasn't cleaned-up!";
+        if (mActionProviders.containsKey(actionType)) {
+            return mActionProviders.get(actionType);
         }
+        PropertyProvider<Action[]> actionProvider = new PropertyProvider<>(actionType);
+        mActionProviders.put(actionType, actionProvider);
+        getManualFillingComponent().registerActionProvider(mWebContents, actionProvider);
+        return actionProvider;
+    }
+
+    private void onActionSelected(Action action) {
+        if (mNativeView == 0) return; // Component was destroyed already.
+        ManualFillingMetricsRecorder.recordActionSelected(action.getActionType());
+        ManualFillingComponentBridgeJni.get().onOptionSelected(
+                mNativeView, ManualFillingComponentBridge.this, action.getActionType());
+    }
+
+    private String getActionTitle(@AccessoryAction int actionType) {
+        switch (actionType) {
+            case AccessoryAction.GENERATE_PASSWORD_AUTOMATIC:
+                return mWindowAndroid.getApplicationContext().getString(
+                        R.string.password_generation_accessory_button);
+            case AccessoryAction.CREDMAN_CONDITIONAL_UI_REENTRY:
+                return mWindowAndroid.getApplicationContext().getString(
+                        R.string.credman_reentry_accessory_button);
+            case AccessoryAction.AUTOFILL_SUGGESTION:
+            case AccessoryAction.COUNT:
+            case AccessoryAction.TOGGLE_SAVE_PASSWORDS:
+            case AccessoryAction.USE_OTHER_PASSWORD:
+            case AccessoryAction.GENERATE_PASSWORD_MANUAL:
+            case AccessoryAction.MANAGE_ADDRESSES:
+            case AccessoryAction.MANAGE_CREDIT_CARDS:
+            case AccessoryAction.MANAGE_PASSWORDS:
+                assert false : "No caption defined for accessory action: " + actionType;
+        }
+        assert false : "Define a title for accessory action: " + actionType;
+        return "";
     }
 
     @NativeMethods
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java
index 79ba4a3..a1ca0c0 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java
@@ -223,6 +223,7 @@
             case AccessoryAction.AUTOFILL_SUGGESTION:
                 return BarItem.Type.SUGGESTION;
             case AccessoryAction.GENERATE_PASSWORD_AUTOMATIC:
+            case AccessoryAction.CREDMAN_CONDITIONAL_UI_REENTRY:
                 return BarItem.Type.ACTION_BUTTON;
             case AccessoryAction.MANAGE_PASSWORDS: // Intentional fallthrough - no view defined.
             case AccessoryAction.COUNT:
diff --git a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
index 393a9ef4..ccea89c 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
+++ b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
@@ -223,12 +223,13 @@
       <message name="IDS_IPH_KEYBOARD_ACCESSORY_SWIPE_FOR_MORE" desc="Text in In-Product-Help bubble suggesting to scroll the accessory to reveal more suggestions.">
         Swipe to see more suggestions
       </message>
+      <!-- TODO(crbug/1444418): Introduce proper string. -->
+      <message translateable="false" name="IDS_CREDMAN_REENTRY_ACCESSORY_BUTTON" desc="Text for the button used to show platform passkeys again.">
+        Show Passkeys
+      </message>
       <message name="IDS_PASSWORD_GENERATION_ACCESSORY_BUTTON" desc="Text for the button used to generate a password.">
         Suggest strong password
       </message>
-      <message name="IDS_PASSWORD_GENERATION_ACCESSORY_BUTTON_SHORT" desc="Shortened text for the button used to generate a password. This is used for devices with small screen.">
-        Suggest password
-      </message>
       <message name="IDS_PASSWORD_ACCESSORY_SHEET_TOGGLE" desc="Description for the icon button used to open and close the password accessory sheet.">
         Show saved passwords and password options
       </message>
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java
index 7db542d..746e54f5 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java
@@ -18,6 +18,7 @@
 import static org.mockito.Mockito.when;
 
 import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.AUTOFILL_SUGGESTION;
+import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.CREDMAN_CONDITIONAL_UI_REENTRY;
 import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.ANIMATION_LISTENER;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BAR_ITEMS;
@@ -241,8 +242,11 @@
                 new PropertyProvider<>(GENERATE_PASSWORD_AUTOMATIC);
         PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
                 new PropertyProvider<>(AUTOFILL_SUGGESTION);
+        PropertyProvider<Action[]> credManProvider =
+                new PropertyProvider<>(CREDMAN_CONDITIONAL_UI_REENTRY);
 
         mCoordinator.registerActionProvider(generationProvider);
+        mCoordinator.registerActionProvider(credManProvider);
         mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
 
         AutofillSuggestion suggestion1 = new AutofillSuggestion("FirstSuggestion", "",
@@ -250,18 +254,22 @@
         AutofillSuggestion suggestion2 = new AutofillSuggestion("SecondSuggestion", "",
                 /* itemTag= */ "", 0, false, 0, false, false, false, /* featureForIPH= */ "");
         Action generationAction = new Action("Generate", GENERATE_PASSWORD_AUTOMATIC, (a) -> {});
+        Action credManAction =
+                new Action("Show Passkeys", CREDMAN_CONDITIONAL_UI_REENTRY, (a) -> {});
         autofillSuggestionProvider.notifyObservers(
                 new AutofillSuggestion[] {suggestion1, suggestion2});
         generationProvider.notifyObservers(new Action[] {generationAction});
+        credManProvider.notifyObservers(new Action[] {credManAction});
 
         // Autofill suggestions should always come last before mandatory tab switcher.
-        assertThat(mModel.get(BAR_ITEMS).size(), is(3));
-        assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(generationAction));
-        assertThat(mModel.get(BAR_ITEMS).get(1), instanceOf(AutofillBarItem.class));
-        AutofillBarItem autofillBarItem1 = (AutofillBarItem) mModel.get(BAR_ITEMS).get(1);
-        assertThat(autofillBarItem1.getSuggestion(), is(suggestion1));
+        assertThat(mModel.get(BAR_ITEMS).size(), is(4));
+        assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(credManAction));
+        assertThat(mModel.get(BAR_ITEMS).get(1).getAction(), is(generationAction));
         assertThat(mModel.get(BAR_ITEMS).get(2), instanceOf(AutofillBarItem.class));
-        AutofillBarItem autofillBarItem2 = (AutofillBarItem) mModel.get(BAR_ITEMS).get(2);
+        AutofillBarItem autofillBarItem1 = (AutofillBarItem) mModel.get(BAR_ITEMS).get(2);
+        assertThat(autofillBarItem1.getSuggestion(), is(suggestion1));
+        assertThat(mModel.get(BAR_ITEMS).get(3), instanceOf(AutofillBarItem.class));
+        AutofillBarItem autofillBarItem2 = (AutofillBarItem) mModel.get(BAR_ITEMS).get(3);
         assertThat(autofillBarItem2.getSuggestion(), is(suggestion2));
     }
 
diff --git a/chrome/android/features/start_surface/java/res/layout/single_tab_view_layout.xml b/chrome/android/features/start_surface/java/res/layout/single_tab_view_layout.xml
index e84a9ff..05505fb 100644
--- a/chrome/android/features/start_surface/java/res/layout/single_tab_view_layout.xml
+++ b/chrome/android/features/start_surface/java/res/layout/single_tab_view_layout.xml
@@ -13,8 +13,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginTop="24dp"
-    android:layout_marginStart="16dp"
-    android:layout_marginEnd="16dp"
+    android:layout_marginStart="@dimen/single_tab_card_lateral_margin"
+    android:layout_marginEnd="@dimen/single_tab_card_lateral_margin"
     android:background="@drawable/single_tab_background"
     android:foreground="?android:attr/selectableItemBackground"
     android:orientation="horizontal">
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 51eac42..0c200fc 100644
--- a/chrome/android/features/start_surface/java/res/values/dimens.xml
+++ b/chrome/android/features/start_surface/java/res/values/dimens.xml
@@ -13,5 +13,6 @@
     <!-- Single Tab card on tablets -->
     <dimen name="single_tab_card_lateral_margin_landscape_tablet">62dp</dimen>
     <dimen name="single_tab_card_lateral_margin_portrait_tablet">14dp</dimen>
+    <dimen name="single_tab_card_lateral_margin">16dp</dimen>
     <dimen name="single_tab_card_start_padding">2dp</dimen>
 </resources>
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherOnTabletMediator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherOnTabletMediator.java
index d7248a7..5b05f47 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherOnTabletMediator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherOnTabletMediator.java
@@ -42,6 +42,7 @@
     private Tab mMostRecentTab;
     private boolean mInitialized;
     private boolean mIsScrollableMvtEnabled;
+    private boolean mIsMultiFeedEnabled;
 
     SingleTabSwitcherOnTabletMediator(PropertyModel propertyModel, Resources resources,
             ActivityLifecycleDispatcher activityLifecycleDispatcher,
@@ -51,9 +52,10 @@
         mResources = resources;
         mTabListFaviconProvider = tabListFaviconProvider;
         mMostRecentTab = mostRecentTab;
+        mIsMultiFeedEnabled = isMultiColumnFeedEnabled;
         mIsScrollableMvtEnabled = isScrollableMvtEnabled;
 
-        if (isMultiColumnFeedEnabled) {
+        if (mIsMultiFeedEnabled) {
             mActivityLifecycleDispatcher = activityLifecycleDispatcher;
             mMarginDefaut = mResources.getDimensionPixelSize(
                     R.dimen.single_tab_card_lateral_margin_landscape_tablet);
@@ -80,12 +82,14 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         // The margin doesn't change when 2 row MV tiles are shown.
-        if (mIsScrollableMvtEnabled) {
+        if (mIsScrollableMvtEnabled && mIsMultiFeedEnabled) {
             updateMargins(newConfig.orientation);
         }
     }
 
-    private void updateMargins(int orientation) {
+    void updateMargins(int orientation) {
+        if (!mIsMultiFeedEnabled) return;
+
         int lateralMargin =
                 mIsScrollableMvtEnabled && orientation == Configuration.ORIENTATION_PORTRAIT
                 ? mMarginSmallPortrait
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java
index 4b2e00b..078f143 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java
@@ -28,7 +28,6 @@
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -39,6 +38,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.R;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.embedder_support.util.UrlConstants;
@@ -372,6 +372,38 @@
         Assert.assertEquals(expectedMarginBottom, marginLayoutParams.bottomMargin);
     }
 
+    @Test
+    @MediumTest
+    @Feature({"StartSurface"})
+    @CommandLineFlags.Add({START_SURFACE_ON_TABLET_TEST_PARAMS})
+    @DisableFeatures({ChromeFeatureList.FEED_MULTI_COLUMN})
+    // clang-format off
+    public void testDefaultSingleTabViewMargin() throws IOException {
+        // clang-format on
+        StartSurfaceTestUtils.prepareTabStateMetadataFile(new int[] {0}, new String[] {TAB_URL}, 0);
+        StartSurfaceTestUtils.startMainActivityFromLauncher(mActivityTestRule);
+        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+        StartSurfaceTestUtils.waitForTabModel(cta);
+
+        // Verifies that a new NTP is created and set as the active Tab.
+        verifyTabCountAndActiveTabUrl(
+                cta, 2, UrlConstants.NTP_URL, true /* expectHomeSurfaceUiShown */);
+        waitForNtpLoaded(cta.getActivityTab());
+
+        NewTabPage ntp = (NewTabPage) cta.getActivityTab().getNativePage();
+        View singleTabView = ntp.getView().findViewById(R.id.single_tab_view);
+
+        Resources res = cta.getResources();
+        int defaultLateralMargin =
+                res.getDimensionPixelSize(R.dimen.single_tab_card_lateral_margin);
+
+        // Verifies that the single Tab card has its original margins.
+        MarginLayoutParams marginLayoutParams =
+                (MarginLayoutParams) singleTabView.getLayoutParams();
+        Assert.assertEquals(defaultLateralMargin, marginLayoutParams.getMarginStart());
+        Assert.assertEquals(defaultLateralMargin, marginLayoutParams.getMarginEnd());
+    }
+
     private void verifyTabCountAndActiveTabUrl(
             ChromeTabbedActivity cta, int tabCount, String url, Boolean expectHomeSurfaceUiShown) {
         Assert.assertEquals(tabCount, cta.getCurrentTabModel().getCount());
diff --git a/chrome/android/java/res/drawable/bookmark_empty_state_illustration.xml b/chrome/android/java/res/drawable/bookmark_empty_state_illustration.xml
new file mode 100644
index 0000000..2de7327
--- /dev/null
+++ b/chrome/android/java/res/drawable/bookmark_empty_state_illustration.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true"
+    android:height="130dp"
+    android:viewportHeight="130"
+    android:viewportWidth="130"
+    android:width="130dp">
+  <path
+      android:fillColor="@color/empty_state_icon_bg_background_color"
+      android:pathData="M63,50m-38,0a38,38 0,1 1,76 0a38,38 0,1 1,-76 0"/>
+  <path
+      android:fillColor="@color/empty_state_icon_bg_foreground_color"
+      android:pathData="M100.45,122L12.38,122C6.65,122 2,117.36 2,111.62L2,81.47C2,49.75 40.82,34.39 62.53,57.53L107.57,105.55C113.42,111.79 109,122 100.45,122Z"/>
+  <path
+      android:pathData="M35,44V97.66C35,99.17 36.61,100.14 37.95,99.42L56.05,89.71C56.65,89.39 57.35,89.39 57.95,89.71L76.05,99.42C77.39,100.14 79,99.17 79,97.66V44C79,42.9 78.1,42 77,42H37C35.9,42 35,42.9 35,44Z"
+      android:strokeColor="@color/empty_state_icon_color"
+      android:strokeWidth="2"/>
+  <path
+      android:pathData="M40,37H82C83.1,37 84,37.9 84,39V96"
+      android:strokeColor="@color/empty_state_icon_color"
+      android:strokeLineCap="round"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"/>
+</vector>
diff --git a/chrome/android/java/res/drawable/reading_list_empty_state_illustration.xml b/chrome/android/java/res/drawable/reading_list_empty_state_illustration.xml
new file mode 100644
index 0000000..ce6db27d
--- /dev/null
+++ b/chrome/android/java/res/drawable/reading_list_empty_state_illustration.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true"
+    android:height="130dp"
+    android:viewportHeight="130"
+    android:viewportWidth="130"
+    android:width="130dp">
+    <path
+        android:fillColor="@color/empty_state_icon_bg_background_color"
+        android:pathData="M2,63.5L2,27.5A16.5,16.5 0,0 1,18.5 11L71.5,11A16.5,16.5 135,0 1,88 27.5L88,63.5A16.5,16.5 0,0 1,71.5 80L18.5,80A16.5,16.5 0,0 1,2 63.5z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_bg_foreground_color"
+        android:pathData="M16.78,92.65L85.42,30.62C93.96,22.91 107.64,26.9 110.67,38L127.65,100.03C130.35,109.86 122.95,119.55 112.76,119.55H27.13C12.99,119.55 6.29,102.13 16.78,92.65Z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M49,51m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M49,66m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M49,81m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M55,50L67,50A1,1 0,0 1,68 51L68,51A1,1 0,0 1,67 52L55,52A1,1 0,0 1,54 51L54,51A1,1 0,0 1,55 50z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M71,50L77,50A1,1 0,0 1,78 51L78,51A1,1 0,0 1,77 52L71,52A1,1 0,0 1,70 51L70,51A1,1 0,0 1,71 50z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M55,65L68,65A1,1 0,0 1,69 66L69,66A1,1 0,0 1,68 67L55,67A1,1 0,0 1,54 66L54,66A1,1 0,0 1,55 65z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M72,65L81,65A1,1 0,0 1,82 66L82,66A1,1 0,0 1,81 67L72,67A1,1 0,0 1,71 66L71,66A1,1 0,0 1,72 65z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M55,80L61,80A1,1 0,0 1,62 81L62,81A1,1 0,0 1,61 82L55,82A1,1 0,0 1,54 81L54,81A1,1 0,0 1,55 80z"/>
+    <path
+        android:fillColor="@color/empty_state_icon_color"
+        android:pathData="M65,80L81,80A1,1 0,0 1,82 81L82,81A1,1 0,0 1,81 82L65,82A1,1 0,0 1,64 81L64,81A1,1 0,0 1,65 80z"/>
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M41,37L89,37A4,4 0,0 1,93 41L93,89A4,4 0,0 1,89 93L41,93A4,4 0,0 1,37 89L37,41A4,4 0,0 1,41 37z"
+        android:strokeColor="@color/empty_state_icon_color"
+        android:strokeWidth="2"/>
+</vector>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
index 0f184e49..1b373c6d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
@@ -137,6 +137,7 @@
 
     @Override
     protected void onDestroy() {
+        mWindowAndroid.destroy();
         mTabShareDelegateSupplier.destroy();
         mShareDelegateSupplier.destroy();
         super.onDestroy();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index 5f9b7fd..5fa6abc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -149,6 +149,7 @@
                 OmniboxFeatures.MODERNIZE_VISUAL_UPDATE_SMALL_BOTTOM_MARGIN,
                 OmniboxFeatures.MODERNIZE_VISUAL_UPDATE_SMALLER_MARGINS,
                 OmniboxFeatures.MODERNIZE_VISUAL_UPDATE_SMALLEST_MARGINS,
+                OmniboxFeatures.TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING,
                 CustomTabIntentDataProvider.AUTO_TRANSLATE_ALLOW_ALL_FIRST_PARTIES,
                 CustomTabIntentDataProvider.AUTO_TRANSLATE_PACKAGE_NAME_ALLOWLIST,
                 CustomTabIntentDataProvider.THIRD_PARTIES_DEFAULT_POLICY,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImpl.java
index 1c8e3c1..146d5e9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImpl.java
@@ -12,21 +12,13 @@
 
 import org.chromium.base.IntentUtils;
 import org.chromium.base.supplier.Supplier;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
-import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
 import org.chromium.components.browser_ui.settings.SettingsLauncher.SettingsFragment;
-import org.chromium.components.embedder_support.util.UrlConstants;
-import org.chromium.components.omnibox.action.HistoryClustersAction;
 import org.chromium.components.omnibox.action.OmniboxAction;
-import org.chromium.components.omnibox.action.OmniboxActionInSuggest;
-import org.chromium.components.omnibox.action.OmniboxActionType;
-import org.chromium.components.omnibox.action.OmniboxPedal;
-import org.chromium.components.omnibox.action.OmniboxPedalType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.content_public.browser.LoadUrlParams;
 
-import java.net.URISyntaxException;
 import java.util.function.Consumer;
 
 /**
@@ -34,7 +26,7 @@
  * TODO(crbug/1418077): repurpose as a OmniboxActionFactoryImpl, move `execute()` to OmniboxAction
  * instances.
  */
-public class ActionChipsDelegateImpl implements ActionChipsDelegate {
+public class ActionChipsDelegateImpl implements OmniboxActionDelegate {
     private final @NonNull Context mContext;
     private final @NonNull SettingsLauncher mSettingsLauncher;
     private final @NonNull Consumer<String> mOpenUrlInExistingTabElseNewTabCb;
@@ -57,71 +49,41 @@
         mOpenHistoryClustersForQueryCb = openHistoryClustersForQueryCb;
     }
 
-    private void executePedalAction(OmniboxPedal pedal) {
-        @OmniboxPedalType
-        int pedalId = pedal.pedalId;
-        switch (pedalId) {
-            case OmniboxPedalType.MANAGE_CHROME_SETTINGS:
-                mSettingsLauncher.launchSettingsActivity(mContext, SettingsFragment.MAIN);
-                break;
-            case OmniboxPedalType.CLEAR_BROWSING_DATA:
-                mSettingsLauncher.launchSettingsActivity(
-                        mContext, SettingsFragment.CLEAR_BROWSING_DATA);
-                break;
-            case OmniboxPedalType.UPDATE_CREDIT_CARD:
-                mSettingsLauncher.launchSettingsActivity(
-                        mContext, SettingsFragment.PAYMENT_METHODS);
-                break;
-            case OmniboxPedalType.RUN_CHROME_SAFETY_CHECK:
-                mSettingsLauncher.launchSettingsActivity(mContext, SettingsFragment.SAFETY_CHECK);
-                break;
-            case OmniboxPedalType.MANAGE_SITE_SETTINGS:
-                mSettingsLauncher.launchSettingsActivity(mContext, SettingsFragment.SITE);
-                break;
-            case OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY:
-                mSettingsLauncher.launchSettingsActivity(mContext, SettingsFragment.ACCESSIBILITY);
-                break;
-            case OmniboxPedalType.VIEW_CHROME_HISTORY:
-                loadPageInCurrentTab(UrlConstants.HISTORY_URL);
-                break;
-            case OmniboxPedalType.PLAY_CHROME_DINO_GAME:
-                loadPageInCurrentTab(UrlConstants.CHROME_DINO_URL);
-                break;
-            case OmniboxPedalType.MANAGE_PASSWORDS:
-                mOpenPasswordSettingsCb.run();
-                break;
-            case OmniboxPedalType.LAUNCH_INCOGNITO:
-                mOpenIncognitoTabCb.run();
-                break;
-        }
-        SuggestionsMetrics.recordPedalUsed(pedalId);
+    /**
+     * TODO(crbug/1418077): turn the class into a factory and remove.
+     */
+    @Override
+    public void execute(OmniboxAction action) {
+        action.execute(this);
     }
 
     @Override
-    public void execute(OmniboxAction action) {
-        switch (action.actionId) {
-            case OmniboxActionType.PEDAL:
-                executePedalAction(OmniboxPedal.from(action));
-                break;
-
-            case OmniboxActionType.HISTORY_CLUSTERS:
-                mOpenHistoryClustersForQueryCb.accept(HistoryClustersAction.from(action).query);
-                break;
-
-            case OmniboxActionType.ACTION_IN_SUGGEST:
-                startActionInSuggestIntent(OmniboxActionInSuggest.from(action));
-                break;
-        }
+    public void openHistoryClustersPage(String query) {
+        mOpenHistoryClustersForQueryCb.accept(query);
     }
 
-    /**
-     * Load the supplied URL in the current tab.
-     * If not possible, open a new tab and load the url there. Try to re-use existing tabs where
-     * possible.
-     *
-     * @param url the page URL to load
-     */
-    private void loadPageInCurrentTab(String url) {
+    @Override
+    public void openIncognitoTab() {
+        mOpenIncognitoTabCb.run();
+    }
+
+    @Override
+    public void openPasswordManager() {
+        mOpenPasswordSettingsCb.run();
+    }
+
+    @Override
+    public void openSettingsPage(@SettingsFragment int fragment) {
+        mSettingsLauncher.launchSettingsActivity(mContext, fragment);
+    }
+
+    @Override
+    public boolean isIncognito() {
+        return mTabSupplier.get().isIncognito();
+    }
+
+    @Override
+    public void loadPageInCurrentTab(String url) {
         var tab = mTabSupplier.get();
         if (tab.isUserInteractable()) {
             tab.loadUrl(new LoadUrlParams(url));
@@ -130,92 +92,16 @@
         }
     }
 
-    /**
-     * Execute an Intent associated with OmniboxActionInSuggest.
-     *
-     * @param actionInSuggest the action to execute the intent for
-     */
-    private void startActionInSuggestIntent(OmniboxActionInSuggest actionInSuggest) {
-        var actionType = actionInSuggest.actionInfo.getActionType();
-        boolean actionStarted = false;
-        Intent intent = null;
-
+    @Override
+    public boolean startActivity(@NonNull Intent intent) {
         try {
-            intent = Intent.parseUri(
-                    actionInSuggest.actionInfo.getActionUri(), Intent.URI_INTENT_SCHEME);
-
-            switch (actionType) {
-                case WEBSITE:
-                    // Rather than invoking an intent that opens a new tab, load the page in the
-                    // current tab.
-                    loadPageInCurrentTab(intent.getDataString());
-                    actionStarted = true;
-                    break;
-
-                case REVIEWS:
-                    assert false : "Pending implementation";
-                    break;
-
-                case CALL:
-                    // Don't call directly. Use `DIAL` instead to let the user decide.
-                    // Note also that ACTION_CALL requires a dedicated permission.
-                    intent.setAction(Intent.ACTION_DIAL);
-                    // Start dialer even if the user is in incognito mode. The intent only pre-dials
-                    // the phone number without ever making the call. This gives the user the chance
-                    // to abandon before making a call.
-                    startActivity(intent);
-                    actionStarted = true;
-                    break;
-
-                case DIRECTIONS:
-                    // Open directions in maps only if maps are installed and the incognito mode is
-                    // not engaged. In all other cases, redirect the action to Browser.
-                    Tab currentTab = mTabSupplier.get();
-                    if (currentTab == null || !currentTab.isIncognito()) {
-                        startActivity(intent);
-                        actionStarted = true;
-                    }
-                    break;
-
-                    // No `default` to capture new variants.
+            if (IntentUtils.intentTargetsSelf(mContext, intent)) {
+                IntentUtils.addTrustedIntentExtras(intent);
             }
-
-            // Record intent started only if it was sent.
-            if (actionStarted) {
-                SuggestionsMetrics.recordActionInSuggestIntentResult(
-                        SuggestionsMetrics.ActionInSuggestIntentResult.SUCCESS);
-            }
-        } catch (URISyntaxException e) {
-            // Never happens. http://b/279756377.
+            mContext.startActivity(intent);
+            return true;
         } catch (ActivityNotFoundException e) {
-            SuggestionsMetrics.recordActionInSuggestIntentResult(
-                    SuggestionsMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND);
-        } finally {
-            // At this point we know that we were unable to launch the target activity.
-            // We may still be able to handle the corresponding action inside the browser.
-            if (!actionStarted) {
-                switch (actionType) {
-                    case DIRECTIONS:
-                        loadPageInCurrentTab(intent.getDataString());
-                        break;
-
-                    case CALL:
-                    case REVIEWS:
-                    case WEBSITE:
-                        // Give up. Don't add the `default` clause though, capture missed variants.
-                        break;
-                }
-            }
         }
-    }
-    /**
-     * Start the activity referenced by the supplied {@link android.content.Intent}.
-     * Decorates the intent with trusted intent extras when the intent references the browser.
-     */
-    private void startActivity(@NonNull Intent intent) {
-        if (IntentUtils.intentTargetsSelf(mContext, intent)) {
-            IntentUtils.addTrustedIntentExtras(intent);
-        }
-        mContext.startActivity(intent);
+        return false;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePrompt.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePrompt.java
index 2e6c208..ab7f1b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePrompt.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePrompt.java
@@ -82,15 +82,15 @@
         mEditorDialog.setShouldTriggerDoneCallbackBeforeCloseAnimation(true);
         AddressEditor.Delegate delegate = new AddressEditor.Delegate() {
             @Override
-            public void onDone(AutofillAddress autofillAddress) {
-                onEdited(autofillAddress);
+            public void onDone(AutofillAddress address) {
+                onEdited(address);
             }
         };
-        mAddressEditor = new AddressEditor(
-                mEditorDialog, delegate, /*saveToDisk=*/false, isUpdate, isMigrationToAccount);
-        AutofillAddress autofillAddress = new AutofillAddress(activity, autofillProfile);
+        mAddressEditor = new AddressEditor(mEditorDialog, delegate,
+                new AutofillAddress(activity, autofillProfile),
+                /*saveToDisk=*/false, isUpdate, isMigrationToAccount);
         mDialogView.findViewById(R.id.edit_button).setOnClickListener(v -> {
-            mAddressEditor.edit(autofillAddress);
+            mAddressEditor.showEditorDialog();
         });
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AddressEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AddressEditor.java
index 0160595d..dd3e36a6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AddressEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AddressEditor.java
@@ -38,7 +38,6 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 
@@ -52,6 +51,9 @@
     private final EditorDialog mEditorDialog;
     private final Delegate mDelegate;
     private final Context mContext;
+    private final AutofillProfile mProfileToEdit;
+    private final AutofillAddress mAddressToEdit;
+    private final boolean mIsProfileNew;
     private final boolean mSaveToDisk;
     private final boolean mIsUpdate;
     private final boolean mIsMigrationToAccount;
@@ -74,8 +76,6 @@
     @Nullable
     private String mCustomDoneButtonText;
     private EditorModel mEditor;
-    private AutofillProfile mProfile;
-    private boolean mIsProfileNew;
 
     /**
      * Delegate used to subscribe to AddressEditor user interactions.
@@ -137,7 +137,7 @@
     }
 
     /**
-     * Builds an address editor.
+     * Builds an address editor for a new address profile.
      *
      * @param editorDialog Editor's view displayed to the user.
      * @param delegate Delegate to react to users interactions with the editor.
@@ -148,10 +148,39 @@
      */
     public AddressEditor(EditorDialog editorDialog, Delegate delegate, boolean saveToDisk,
             boolean isUpdate, boolean isMigrationToAccount) {
-        Objects.requireNonNull(editorDialog, "editor dialog can't be null");
         mEditorDialog = editorDialog;
         mDelegate = delegate;
         mContext = editorDialog.getContext();
+        mProfileToEdit = new AutofillProfile();
+        mAddressToEdit = new AutofillAddress(editorDialog.getContext(), mProfileToEdit);
+        mIsProfileNew = true;
+        mSaveToDisk = saveToDisk;
+        mIsUpdate = isUpdate;
+        mIsMigrationToAccount = isMigrationToAccount;
+        mPhoneFormatter = new PhoneNumberUtil.CountryAwareFormatTextWatcher();
+        mPhoneValidator = new CountryAwarePhoneNumberValidator(true);
+    }
+
+    /**
+     * Builds an address editor for an existing address profile.
+     *
+     * @param editorDialog Editor's view displayed to the user.
+     * @param delegate Delegate to react to users interactions with the editor.
+     * @param addressToEdit Address the user wants to modify.
+     * @param saveToDisk Whether to save changes to disk after editing.
+     * @param isUpdate Whether an existing address profile is being edited.
+     * @param isMigrationToAccount Whether this editor is shown during address profile migration to
+     *         Google account.
+     */
+    public AddressEditor(EditorDialog editorDialog, Delegate delegate,
+            AutofillAddress addressToEdit, boolean saveToDisk, boolean isUpdate,
+            boolean isMigrationToAccount) {
+        mEditorDialog = editorDialog;
+        mDelegate = delegate;
+        mContext = editorDialog.getContext();
+        mProfileToEdit = addressToEdit.getProfile();
+        mAddressToEdit = addressToEdit;
+        mIsProfileNew = false;
         mSaveToDisk = saveToDisk;
         mIsUpdate = isUpdate;
         mIsMigrationToAccount = isMigrationToAccount;
@@ -201,19 +230,12 @@
      *
      * TODO(crbug.com/1421056): Split this method for better code readability.
      */
-    public void edit(@Nullable final AutofillAddress toEdit) {
+    public void showEditorDialog() {
         if (mAutofillProfileBridge == null) mAutofillProfileBridge = new AutofillProfileBridge();
 
-        mIsProfileNew = toEdit == null;
         final String editTitle = mIsProfileNew
                 ? mContext.getString(R.string.autofill_create_profile)
                 : mContext.getString(R.string.autofill_edit_address_dialog_title);
-        // When creating a new autofill profile, we use the country code of the default locale on
-        // the device.
-        final AutofillAddress address =
-                mIsProfileNew ? new AutofillAddress(mContext, new AutofillProfile()) : toEdit;
-
-        mProfile = address.getProfile();
 
         @Nullable
         final String footerMessageText = getSourceNoticeText();
@@ -255,7 +277,7 @@
 
         // Country dropdown is cached, so the selected item needs to be updated for the new profile
         // that's being edited. This will not fire the dropdown callback.
-        mCountryField.setValue(AutofillAddress.getCountryCode(mProfile));
+        mCountryField.setValue(AutofillAddress.getCountryCode(mProfileToEdit));
 
         // Phone number validator and formatter are cached, so their country code needs to be
         // updated for the new profile that's being edited.
@@ -271,7 +293,7 @@
                         mContext.getString(R.string.autofill_profile_editor_honorific_prefix));
             }
             // Retrieve and set the honorific prefix value.
-            mHonorificField.setValue(mProfile.getHonorificPrefix());
+            mHonorificField.setValue(mProfileToEdit.getHonorificPrefix());
         }
 
         // There's a finite number of fields for address editing. Changing the country will re-order
@@ -291,7 +313,7 @@
         }
         // Phone number field is cached, so its value needs to be updated for every new profile
         // that's being edited.
-        mPhoneField.setValue(mProfile.getPhoneNumber());
+        mPhoneField.setValue(mProfileToEdit.getPhoneNumber());
 
         if (mEmailField == null) {
             mEmailField = EditorFieldModel.createTextInput(EditorFieldModel.INPUT_TYPE_HINT_EMAIL,
@@ -302,7 +324,7 @@
                     EditorFieldModel.LENGTH_COUNTER_LIMIT_NONE, null /* value */);
         }
         // Retrieve and set the email address field.
-        mEmailField.setValue(mProfile.getEmailAddress());
+        mEmailField.setValue(mProfileToEdit.getEmailAddress());
 
         if (ChromeFeatureList.isEnabled(
                     ChromeFeatureList.AUTOFILL_ADDRESS_PROFILE_SAVE_PROMPT_NICKNAME_SUPPORT)) {
@@ -313,25 +335,26 @@
             }
         }
 
-        // If the user clicks [Cancel], send |toEdit| address back to the caller, which was the
-        // original state (could be null, a complete address, a partial address).
+        // If the user clicks [Cancel], send |addressToEdit| address back to the caller, which was
+        // the original state (could be null, a complete address, a partial address).
         mEditor.setCancelCallback(mDelegate::onCancel);
 
         // If the user clicks [Done], save changes on disk, mark the address "complete" if possible,
         // and send it back to the caller.
         mEditor.setDoneCallback(() -> {
-            commitChanges(mProfile);
+            commitChanges(mProfileToEdit);
 
             // The address cannot be marked "complete" because it has not been checked
             // for all required fields.
-            address.updateAddress(mProfile);
+            mAddressToEdit.updateAddress(mProfileToEdit);
 
-            mDelegate.onDone(address);
+            mDelegate.onDone(mAddressToEdit);
         });
 
         // This should be called when all required fields are put in mAddressField.
         setAddressFieldValuesFromCache();
-        addAddressFieldsToEditor(mCountryField.getValue().toString(), mProfile.getLanguageCode());
+        addAddressFieldsToEditor(
+                mCountryField.getValue().toString(), mProfileToEdit.getLanguageCode());
         mEditorDialog.show(mEditor);
     }
 
@@ -374,7 +397,7 @@
 
         // Save the edited autofill profile locally.
         if (mSaveToDisk) {
-            profile.setGUID(PersonalDataManager.getInstance().setProfileToLocal(mProfile));
+            profile.setGUID(PersonalDataManager.getInstance().setProfileToLocal(mProfileToEdit));
         }
 
         if (profile.getGUID().isEmpty()) {
@@ -433,7 +456,8 @@
         // Address fields are cached, so their values need to be updated for every new profile
         // that's being edited.
         for (Map.Entry<Integer, EditorFieldModel> entry : mAddressFields.entrySet()) {
-            entry.getValue().setValue(AutofillAddress.getProfileField(mProfile, entry.getKey()));
+            entry.getValue().setValue(
+                    AutofillAddress.getProfileField(mProfileToEdit, entry.getKey()));
         }
     }
 
@@ -482,7 +506,7 @@
             return true;
         }
 
-        if (mProfile.getSource() == Source.ACCOUNT && !mIsUpdate) {
+        if (mProfileToEdit.getSource() == Source.ACCOUNT && !mIsUpdate) {
             return true; // Only already saved address can be updated.
         }
 
@@ -493,7 +517,7 @@
     }
 
     private boolean isAlreadySavedInAccount() {
-        return mProfile.getSource() == Source.ACCOUNT && mIsUpdate;
+        return mProfileToEdit.getSource() == Source.ACCOUNT && mIsUpdate;
     }
 
     private boolean isAccountAddressProfile() {
@@ -515,7 +539,7 @@
         if (!component.isRequired) return false;
 
         boolean isContentEmpty =
-                TextUtils.isEmpty(AutofillAddress.getProfileField(mProfile, component.id));
+                TextUtils.isEmpty(AutofillAddress.getProfileField(mProfileToEdit, component.id));
         // Already empty fields in existing address profiles are made optional even if they
         // are required by account storage rules. This allows users to save address profiles
         // as is without making them more complete during the process.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillProfilesFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillProfilesFragment.java
index 626f4f4..376440c3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillProfilesFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillProfilesFragment.java
@@ -46,6 +46,26 @@
 public class AutofillProfilesFragment
         extends PreferenceFragmentCompat implements PersonalDataManager.PersonalDataManagerObserver,
                                                     FragmentHelpAndFeedbackLauncher {
+    private static AddressEditor.Delegate sAddressEditorDelegate = new AddressEditor.Delegate() {
+        // User has either created a new address, or edited an existing address.
+        // We should save changes in any case.
+        @Override
+        public void onDone(AutofillAddress address) {
+            PersonalDataManager.getInstance().setProfile(address.getProfile());
+            SettingsAutofillAndPaymentsObserver.getInstance().notifyOnAddressUpdated(address);
+            if (sObserverForTest != null) {
+                sObserverForTest.onEditorReadyToEdit();
+            }
+        }
+
+        // User canceled edited meaning that |autofillAddress| has stayed intact.
+        @Override
+        public void onCancel() {
+            if (sObserverForTest != null) {
+                sObserverForTest.onEditorReadyToEdit();
+            }
+        }
+    };
     private static EditorObserverForTest sObserverForTest;
     static final String PREF_NEW_PROFILE = "new_profile";
     private @Nullable EditorDialog mEditorDialog;
@@ -192,21 +212,26 @@
 
     @Override
     public void onDisplayPreferenceDialog(Preference preference) {
-        if (preference instanceof AutofillProfileEditorPreference) {
-            String guid = ((AutofillProfileEditorPreference) preference).getGUID();
-            mEditorDialog = prepareEditorDialog(guid);
-            AutofillAddress autofillAddress = null;
-            if (guid != null) {
-                AutofillProfile profile = PersonalDataManager.getInstance().getProfile(guid);
-                if (profile != null) {
-                    autofillAddress = new AutofillAddress(getActivity(), profile);
-                }
-            }
-            editAddress(mEditorDialog, autofillAddress);
+        if (!(preference instanceof AutofillProfileEditorPreference)) {
+            super.onDisplayPreferenceDialog(preference);
             return;
         }
-
-        super.onDisplayPreferenceDialog(preference);
+        AutofillProfileEditorPreference editorPreference =
+                (AutofillProfileEditorPreference) preference;
+        mEditorDialog = prepareEditorDialog(editorPreference.getGUID());
+        AutofillAddress autofillAddress = getAutofillAddress(editorPreference);
+        if (autofillAddress == null) {
+            AddressEditor addressEditor = new AddressEditor(mEditorDialog, sAddressEditorDelegate,
+                    /*saveToDisk=*/true, /*isUpdate=*/autofillAddress != null,
+                    /*isMigrationToAccount=*/false);
+            addressEditor.showEditorDialog();
+        } else {
+            AddressEditor addressEditor =
+                    new AddressEditor(mEditorDialog, sAddressEditorDelegate, autofillAddress,
+                            /*saveToDisk=*/true, /*isUpdate=*/autofillAddress != null,
+                            /*isMigrationToAccount=*/false);
+            addressEditor.showEditorDialog();
+        }
     }
 
     @VisibleForTesting
@@ -223,32 +248,17 @@
                 getActivity(), runnable, Profile.getLastUsedRegularProfile(), false);
     }
 
-    private void editAddress(EditorDialog dialog, AutofillAddress autofillAddress) {
-        AddressEditor.Delegate delegate = new AddressEditor.Delegate() {
-            // User has either created a new address, or edited an existing address.
-            // We should save changes in any case.
-            @Override
-            public void onDone(AutofillAddress address) {
-                PersonalDataManager.getInstance().setProfile(address.getProfile());
-                SettingsAutofillAndPaymentsObserver.getInstance().notifyOnAddressUpdated(address);
-                if (sObserverForTest != null) {
-                    sObserverForTest.onEditorReadyToEdit();
-                }
-            }
-
-            // User canceled edited meaning that |autofillAddress| has stayed intact.
-            @Override
-            public void onCancel() {
-                if (sObserverForTest != null) {
-                    sObserverForTest.onEditorReadyToEdit();
-                }
-            }
-        };
-        AddressEditor addressEditor = new AddressEditor(dialog, delegate,
-                /*saveToDisk=*/true, /*isUpdate=*/autofillAddress != null,
-                /*isMigrationToAccount=*/false);
-
-        addressEditor.edit(autofillAddress);
+    @Nullable
+    private AutofillAddress getAutofillAddress(AutofillProfileEditorPreference preference) {
+        String guid = preference.getGUID();
+        if (guid == null) {
+            return null;
+        }
+        AutofillProfile profile = PersonalDataManager.getInstance().getProfile(guid);
+        if (profile == null) {
+            return null;
+        }
+        return new AutofillAddress(getActivity(), profile);
     }
 
     private boolean isAddressSyncEnabled() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
index 81d03e5..f61121ef 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
@@ -27,6 +27,7 @@
 import org.chromium.chrome.browser.bookmarks.BookmarkListEntry.ViewType;
 import org.chromium.chrome.browser.commerce.ShoppingFeatures;
 import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.native_page.BasicNativePage;
@@ -109,7 +110,15 @@
         SelectableListLayout<BookmarkId> selectableList =
                 mMainView.findViewById(R.id.selectable_list);
         mSelectableListLayout = selectableList;
-        mSelectableListLayout.initializeEmptyView(R.string.bookmarks_folder_empty);
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.EMPTY_STATES)) {
+            mSelectableListLayout.initializeEmptyStateView(
+                    R.drawable.bookmark_empty_state_illustration,
+                    R.string.bookmark_manager_empty_state,
+                    R.string.bookmark_manager_back_to_page_by_adding_bookmark);
+        } else {
+            mSelectableListLayout.initializeEmptyView(R.string.bookmarks_folder_empty);
+        }
+
         ModelList modelList = new ModelList();
         DragReorderableRecyclerViewAdapter dragReorderableRecyclerViewAdapter =
                 new DragReorderableRecyclerViewAdapter(context, modelList);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
index ef5f5d6..5a233d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
@@ -33,6 +33,7 @@
 import org.chromium.chrome.browser.bookmarks.BookmarkUiPrefs.Observer;
 import org.chromium.chrome.browser.bookmarks.BookmarkUiState.BookmarkUiMode;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksReader;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
@@ -238,9 +239,26 @@
             } else if (getCurrentFolderId().getType() == BookmarkType.READING_LIST) {
                 TrackerFactory.getTrackerForProfile(mProfile).notifyEvent(
                         EventConstants.READ_LATER_BOOKMARK_FOLDER_OPENED);
-                getSelectableListLayout().setEmptyViewText(R.string.reading_list_empty_list_title);
+                if (ChromeFeatureList.isEnabled(ChromeFeatureList.EMPTY_STATES)) {
+                    getSelectableListLayout().setEmptyStateImageRes(
+                            R.drawable.reading_list_empty_state_illustration);
+                    getSelectableListLayout().setEmptyStateViewText(
+                            R.string.reading_list_manager_empty_state,
+                            R.string.reading_list_manager_save_page_to_read_later);
+                } else {
+                    getSelectableListLayout().setEmptyViewText(
+                            R.string.reading_list_empty_list_title);
+                }
             } else {
-                getSelectableListLayout().setEmptyViewText(R.string.bookmarks_folder_empty);
+                if (ChromeFeatureList.isEnabled(ChromeFeatureList.EMPTY_STATES)) {
+                    getSelectableListLayout().setEmptyStateImageRes(
+                            R.drawable.bookmark_empty_state_illustration);
+                    getSelectableListLayout().setEmptyStateViewText(
+                            R.string.bookmark_manager_empty_state,
+                            R.string.bookmark_manager_back_to_page_by_adding_bookmark);
+                } else {
+                    getSelectableListLayout().setEmptyViewText(R.string.bookmarks_folder_empty);
+                }
             }
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java
index 1f65d92..6e47aa4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java
@@ -7,7 +7,6 @@
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -30,6 +29,7 @@
 import org.chromium.components.commerce.core.ShoppingService;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
+import org.chromium.ui.accessibility.AccessibilityState;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -118,9 +118,7 @@
         boolean shown =
                 mBottomSheetController.requestShowContent(mBottomSheetContent, /* animate= */ true);
 
-        AccessibilityManager am =
-                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (!am.isTouchExplorationEnabled()) {
+        if (!AccessibilityState.isTouchExplorationEnabled()) {
             setupAutodismiss();
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandler.java
index 10ebcd4..ca76f0a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandler.java
@@ -4,7 +4,7 @@
 
 package org.chromium.chrome.browser.bookmarks;
 
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.commerce.ShoppingFeatures;
 import org.chromium.chrome.browser.sync.SyncService;
 import org.chromium.chrome.browser.sync.SyncService.SyncStateChangedListener;
 import org.chromium.components.bookmarks.BookmarkId;
@@ -67,7 +67,7 @@
             bookmarkListEntries.add(bookmarkListEntry);
         }
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHOPPING_LIST)) {
+        if (ShoppingFeatures.isShoppingListEligible()) {
             bookmarkListEntries.add(BookmarkListEntry.createDivider());
             bookmarkListEntries.add(BookmarkListEntry.createShoppingFilter());
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
index 3ac723a..81eabff 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
@@ -218,4 +218,4 @@
     public void initializeNativeForTesting() {
         this.initializeNative();
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS
index 2bc872a..b266164 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS
@@ -26,6 +26,7 @@
     "-chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
     "-chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java",
     "+components/feature_engagement",
+    "+components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionDelegate.java",
   ],
   'ToolbarButtonInProductHelpController.java': [
     "+chrome/android/java/src/org/chromium/chrome/browser",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 39fd574..db57204 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -85,7 +85,6 @@
 import org.chromium.chrome.browser.omnibox.OverrideUrlLoadingDelegate;
 import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.base.HistoryClustersProcessor.OpenHistoryClustersDelegate;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.page_info.ChromePageInfo;
@@ -159,6 +158,7 @@
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.feature_engagement.FeatureConstants;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.components.page_info.PageInfoController.OpenedFromSource;
 import org.chromium.components.search_engines.TemplateUrl;
 import org.chromium.components.search_engines.TemplateUrlService;
@@ -454,7 +454,7 @@
             @NonNull Supplier<MerchantTrustSignalsCoordinator>
                     merchantTrustSignalsCoordinatorSupplier,
             OneshotSupplier<TabReparentingController> tabReparentingControllerSupplier,
-            @NonNull ActionChipsDelegate actionChipsDelegate,
+            @NonNull OmniboxActionDelegate omniboxActionDelegate,
             Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
             boolean initializeWithIncognitoColors, @Nullable BackPressManager backPressManager,
             @NonNull OpenHistoryClustersDelegate openHistoryClustersDelegate) {
@@ -666,7 +666,7 @@
                             AdaptiveToolbarButtonVariant.VOICE;
                     },
                     merchantTrustSignalsCoordinatorSupplier,
-                    actionChipsDelegate, mControlsVisibilityDelegate,
+                    omniboxActionDelegate, mControlsVisibilityDelegate,
                     ChromePureJavaExceptionReporter::reportJavaException, backPressManager,
                     toolbarLayout, openHistoryClustersDelegate);
             // clang-format on
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java
index 8f4dc7b..51a9fa09 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java
@@ -9,7 +9,6 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.os.Build;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
@@ -21,6 +20,7 @@
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
 import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
+import org.chromium.ui.accessibility.AccessibilityState;
 
 /**
  * Manages notifications displayed while tracing and once tracing is complete.
@@ -102,11 +102,9 @@
         String message = String.format(
                 MSG_ACTIVE_NOTIFICATION_MESSAGE, sTracingActiveNotificationBufferPercentage);
 
-        // We can't update the notification if accessibility is enabled as this may interfere with
-        // selecting the stop button, so choose a different message.
-        AccessibilityManager am =
-                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (am.isEnabled() && am.isTouchExplorationEnabled()) {
+        // We can't update the notification if touch exploration is enabled as this may interfere
+        // with selecting the stop button, so choose a different message.
+        if (AccessibilityState.isTouchExplorationEnabled()) {
             message = MSG_ACTIVE_NOTIFICATION_ACCESSIBILITY_MESSAGE;
         }
 
@@ -131,11 +129,9 @@
         assert (sTracingActiveNotificationBuilder != null);
         Context context = ContextUtils.getApplicationContext();
 
-        // Don't update the notification if accessibility is enabled as this may interfere with
+        // Don't update the notification if touch exploration is enabled as this may interfere with
         // selecting the stop button.
-        AccessibilityManager am =
-                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (am.isEnabled() && am.isTouchExplorationEnabled()) return;
+        if (AccessibilityState.isTouchExplorationEnabled()) return;
 
         int newPercentage = Math.round(bufferUsagePercentage * 100);
         if (sTracingActiveNotificationBufferPercentage == newPercentage) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index c626877..708e2b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -92,7 +92,6 @@
 import org.chromium.chrome.browser.messages.MessagesResourceMapperInitializer;
 import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.base.HistoryClustersProcessor.OpenHistoryClustersDelegate;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceInteractionSource;
@@ -165,6 +164,7 @@
 import org.chromium.components.messages.MessageContainer;
 import org.chromium.components.messages.MessageDispatcherProvider;
 import org.chromium.components.messages.MessagesFactory;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.components.ukm.UkmRecorder;
 import org.chromium.content_public.browser.ActionModeCallbackHelper;
 import org.chromium.content_public.browser.BrowserContextHandle;
@@ -304,7 +304,7 @@
     private final Supplier<TabContentManager> mTabContentManagerSupplier;
     private final IntentRequestTracker mIntentRequestTracker;
     private final OneshotSupplier<TabReparentingController> mTabReparentingControllerSupplier;
-    private final ActionChipsDelegate mActionChipsDelegate;
+    private final OmniboxActionDelegate mOmniboxActionDelegate;
     private final boolean mInitializeUiWithIncognitoColors;
     private HistoryClustersCoordinator mHistoryClustersCoordinator;
     private final Supplier<EphemeralTabCoordinator> mEphemeralTabCoordinatorSupplier;
@@ -431,7 +431,7 @@
         mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(this);
         mActivityTabProvider = tabProvider;
 
-        mActionChipsDelegate = new ActionChipsDelegateImpl(mActivity, mActivityTabProvider,
+        mOmniboxActionDelegate = new ActionChipsDelegateImpl(mActivity, mActivityTabProvider,
                 new SettingsLauncherImpl(),
                 // TODO(ender): phase out callbacks when the modules below are components.
                 // Open URL in an existing, else new regular tab.
@@ -1226,7 +1226,7 @@
                     mStartSurfaceParentTabSupplier, mBottomSheetController, mIsWarmOnResumeSupplier,
                     mTabContentManagerSupplier.get(), mTabCreatorManagerSupplier.get(),
                     mSnackbarManagerSupplier.get(), getMerchantTrustSignalsCoordinatorSupplier(),
-                    mTabReparentingControllerSupplier, mActionChipsDelegate,
+                    mTabReparentingControllerSupplier, mOmniboxActionDelegate,
                     mEphemeralTabCoordinatorSupplier, mInitializeUiWithIncognitoColors,
                     mBackPressManager, openHistoryClustersDelegate);
             if (!mSupportsAppMenuSupplier.getAsBoolean()) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
index f8fde31..565e6b3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
@@ -81,6 +81,7 @@
 import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
 import org.chromium.chrome.browser.bookmarks.PowerBookmarkShoppingItemRow;
 import org.chromium.chrome.browser.bookmarks.TestingDelegate;
+import org.chromium.chrome.browser.commerce.ShoppingFeatures;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
@@ -190,6 +191,7 @@
 
     @Before
     public void setUp() {
+        ShoppingFeatures.setShoppingListEligibleForTesting(false);
         mActivityTestRule.startMainActivityOnBlankPage();
         runOnUiThreadBlocking(() -> {
             mBookmarkModel = mActivityTestRule.getActivity().getBookmarkModelForTesting();
@@ -1703,8 +1705,8 @@
     @Test
     @MediumTest
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
-    @Features.EnableFeatures({ChromeFeatureList.SHOPPING_LIST})
     public void testShoppingFilterInBookmarks() throws InterruptedException, ExecutionException {
+        ShoppingFeatures.setShoppingListEligibleForTesting(true);
         BookmarkPromoHeader.forcePromoStateForTesting(SyncPromoState.NO_PROMO);
         openBookmarkManager();
         BookmarkTestUtil.waitForBookmarkModelLoaded();
@@ -1721,9 +1723,9 @@
     @Test
     @MediumTest
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
-    @Features.EnableFeatures({ChromeFeatureList.SHOPPING_LIST})
     public void testShoppingDataPresentButFeatureDisabled()
             throws InterruptedException, ExecutionException {
+        ShoppingFeatures.setShoppingListEligibleForTesting(true);
         BookmarkId id = addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage);
         PowerBookmarkMeta.Builder meta = PowerBookmarkMeta.newBuilder().setShoppingSpecifics(
                 ShoppingSpecifics.newBuilder().setProductClusterId(1234L).build());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AddressEditorRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AddressEditorRenderTest.java
index 7e8d639..d0c17047 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AddressEditorRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AddressEditorRenderTest.java
@@ -202,7 +202,7 @@
                     new EditorDialog(getActivity(), /*deleteRunnable=*/null, mProfile);
             AddressEditor addressEditor = new AddressEditor(dialog, mDelegate, /*saveToDisk=*/false,
                     /*isUpdate=*/false, /*isMigrationToAccount=*/false);
-            addressEditor.edit(null);
+            addressEditor.showEditorDialog();
             return dialog.getDataViewForTest();
         });
         mRenderTestRule.render(editor, "edit_new_address_profile");
@@ -218,7 +218,7 @@
                     new EditorDialog(getActivity(), /*deleteRunnable=*/null, mProfile);
             AddressEditor addressEditor = new AddressEditor(dialog, mDelegate, /*saveToDisk=*/false,
                     /*isUpdate=*/false, /*isMigrationToAccount=*/false);
-            addressEditor.edit(null);
+            addressEditor.showEditorDialog();
             return dialog.getDataViewForTest();
         });
         mRenderTestRule.render(editor, "edit_new_account_address_profile");
@@ -232,9 +232,10 @@
             when(mPersonalDataManager.isEligibleForAddressAccountStorage()).thenReturn(true);
             EditorDialog dialog =
                     new EditorDialog(getActivity(), /*deleteRunnable=*/null, mProfile);
-            AddressEditor addressEditor = new AddressEditor(dialog, mDelegate, /*saveToDisk=*/false,
+            AddressEditor addressEditor = new AddressEditor(dialog, mDelegate,
+                    new AutofillAddress(getActivity(), sLocalProfile), /*saveToDisk=*/false,
                     /*isUpdate=*/false, /*isMigrationToAccount=*/false);
-            addressEditor.edit(new AutofillAddress(getActivity(), sLocalProfile));
+            addressEditor.showEditorDialog();
             return dialog.getDataViewForTest();
         });
         mRenderTestRule.render(editor, "edit_local_or_syncable_address_profile");
@@ -248,9 +249,10 @@
             when(mPersonalDataManager.isEligibleForAddressAccountStorage()).thenReturn(true);
             EditorDialog dialog =
                     new EditorDialog(getActivity(), /*deleteRunnable=*/null, mProfile);
-            AddressEditor addressEditor = new AddressEditor(dialog, mDelegate, /*saveToDisk=*/false,
+            AddressEditor addressEditor = new AddressEditor(dialog, mDelegate,
+                    new AutofillAddress(getActivity(), sAccountProfile), /*saveToDisk=*/false,
                     /*isUpdate=*/false, /*isMigrationToAccount=*/false);
-            addressEditor.edit(new AutofillAddress(getActivity(), sAccountProfile));
+            addressEditor.showEditorDialog();
             return dialog.getDataViewForTest();
         });
         mRenderTestRule.render(editor, "edit_account_address_profile");
@@ -264,9 +266,10 @@
             when(mPersonalDataManager.isEligibleForAddressAccountStorage()).thenReturn(true);
             EditorDialog dialog =
                     new EditorDialog(getActivity(), /*deleteRunnable=*/null, mProfile);
-            AddressEditor addressEditor = new AddressEditor(dialog, mDelegate, /*saveToDisk=*/false,
+            AddressEditor addressEditor = new AddressEditor(dialog, mDelegate,
+                    new AutofillAddress(getActivity(), sLocalProfile), /*saveToDisk=*/false,
                     /*isUpdate=*/false, /*isMigrationToAccount=*/true);
-            addressEditor.edit(new AutofillAddress(getActivity(), sLocalProfile));
+            addressEditor.showEditorDialog();
             return dialog.getDataViewForTest();
         });
         mRenderTestRule.render(editor, "migrate_local_or_syncable_address_profile");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 97f080da..6952d259 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -56,6 +56,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.PackageManagerUtils;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -1052,6 +1053,12 @@
             Criteria.checkThat(mActivityTestRule.getActivity().getActivityTab().getUrl().getSpec(),
                     Matchers.is(mTestServer.getURL(HELLO_PAGE)));
         });
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(
+                    RedirectHandlerTabHelper
+                            .getOrCreateHandlerFor(mActivityTestRule.getActivity().getActivityTab())
+                            .shouldNotOverrideUrlLoading());
+        });
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
index 1cc8080..0eff452 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
@@ -43,7 +43,6 @@
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionView;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
@@ -58,6 +57,7 @@
 import org.chromium.components.omnibox.AutocompleteResult;
 import org.chromium.components.omnibox.GroupsProto.GroupsInfo;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.modaldialog.ModalDialogManager;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index df370302..8514b94 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -61,7 +61,6 @@
 import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
 import org.chromium.chrome.browser.omnibox.UrlBar;
 import org.chromium.chrome.browser.omnibox.suggestions.CachedZeroSuggestionsManager;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionView;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -87,6 +86,7 @@
 import org.chromium.components.omnibox.AutocompleteMatchBuilder;
 import org.chromium.components.omnibox.AutocompleteResult;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.components.search_engines.TemplateUrl;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTest.java
index 8d1aa53..54947b9d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTest.java
@@ -9,7 +9,6 @@
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
@@ -23,7 +22,6 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.sync.SyncTestUtil;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.ConsentLevel;
@@ -42,8 +40,6 @@
 public class SyncTest {
     @Rule
     public SyncTestRule mSyncTestRule = new SyncTestRule();
-    @Rule
-    public TestRule mProcessorRule = new Features.JUnitProcessor();
 
     /**
      * Waits until {@link SyncService#isSyncingUnencryptedUrls} returns desired value.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImplUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImplUnitTest.java
index b7a9d34..3178a371 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImplUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/omnibox/ActionChipsDelegateImplUnitTest.java
@@ -5,21 +5,15 @@
 package org.chromium.chrome.browser.app.omnibox;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.any;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
-import android.net.Uri;
-
-import com.google.common.collect.ImmutableSet;
 
 import org.junit.After;
 import org.junit.Before;
@@ -30,27 +24,18 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
-import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowApplication;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.IntentUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
-import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
 import org.chromium.components.browser_ui.settings.SettingsLauncher.SettingsFragment;
-import org.chromium.components.embedder_support.util.UrlConstants;
-import org.chromium.components.omnibox.EntityInfoProto;
-import org.chromium.components.omnibox.action.HistoryClustersAction;
-import org.chromium.components.omnibox.action.OmniboxAction;
-import org.chromium.components.omnibox.action.OmniboxActionInSuggest;
-import org.chromium.components.omnibox.action.OmniboxActionType;
-import org.chromium.components.omnibox.action.OmniboxPedal;
-import org.chromium.components.omnibox.action.OmniboxPedalType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.TestActivity;
 
-import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -58,18 +43,6 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 public class ActionChipsDelegateImplUnitTest {
-    /** List of all supported OmniboxPedalTypes. */
-    public static final Set<Integer> SUPPORTED_PEDALS = ImmutableSet.of(
-            OmniboxPedalType.CLEAR_BROWSING_DATA, OmniboxPedalType.MANAGE_PASSWORDS,
-            OmniboxPedalType.UPDATE_CREDIT_CARD, OmniboxPedalType.LAUNCH_INCOGNITO,
-            OmniboxPedalType.RUN_CHROME_SAFETY_CHECK, OmniboxPedalType.MANAGE_SITE_SETTINGS,
-            OmniboxPedalType.MANAGE_CHROME_SETTINGS, OmniboxPedalType.VIEW_CHROME_HISTORY,
-            OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY, OmniboxPedalType.PLAY_CHROME_DINO_GAME);
-
-    /** List of all supported OmniboxActionTypes. */
-    public static final Set<Integer> SUPPORTED_ACTIONS =
-            ImmutableSet.of(OmniboxActionType.PEDAL, OmniboxActionType.HISTORY_CLUSTERS);
-
     public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
     private @Mock Consumer<String> mMockOpenUrl;
     private @Mock Consumer<String> mMockOpenHistoryClustersUi;
@@ -77,27 +50,17 @@
     private @Mock Runnable mMockOpenPasswordSettings;
     private @Mock SettingsLauncher mMockSettingsLauncher;
     private @Mock Tab mTab;
-    private @Mock Context mMockContext;
-    private ArgumentCaptor<Intent> mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
-
-    private ShadowLooper mShadowLooper;
-    private ActionChipsDelegate mDelegate;
+    private Context mContext;
+    private OmniboxActionDelegate mDelegate;
 
     @Before
     public void setUp() {
-        mShadowLooper = ShadowLooper.shadowMainLooper();
-
-        mDelegate = new ActionChipsDelegateImpl(mMockContext,
+        mContext = ContextUtils.getApplicationContext();
+        mDelegate = new ActionChipsDelegateImpl(mContext,
                 ()
                         -> mTab,
                 mMockSettingsLauncher, mMockOpenUrl, mMockOpenIncognitoPage,
                 mMockOpenPasswordSettings, mMockOpenHistoryClustersUi);
-
-        doReturn(ContextUtils.getApplicationContext()).when(mMockContext).getApplicationContext();
-        doReturn(ContextUtils.getApplicationContext().getPackageName())
-                .when(mMockContext)
-                .getPackageName();
-        doReturn(true).when(mTab).isUserInteractable();
     }
 
     @After
@@ -108,312 +71,91 @@
         verifyNoMoreInteractions(mMockOpenUrl);
     }
 
-    /**
-     * Verify that a histogram recording the use of particular type of OmniboxPedal has been
-     * recorded.
-     *
-     * @param type The type of Pedal to check for.
-     */
-    private void checkOmniboxPedalUsageRecorded(@OmniboxPedalType int type) {
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Omnibox.SuggestionUsed.Pedal", type));
-        assertEquals(1,
-                RecordHistogram.getHistogramTotalCountForTesting("Omnibox.SuggestionUsed.Pedal"));
-    }
-
-    /**
-     * Create Omnibox Pedal action of a given type.
-     */
-    private OmniboxAction buildPedal(@OmniboxPedalType int type) {
-        return new OmniboxPedal("hint", type);
-    }
-
-    /**
-     * Create HistoryCluster Action with a supplied query.
-     */
-    private OmniboxAction buildHistoryClustersAction(String query) {
-        return new HistoryClustersAction("hint", query);
-    }
-
-    /**
-     * Create Action in Suggest with a supplied definition.
-     */
-    private OmniboxAction buildActionInSuggest(
-            EntityInfoProto.ActionInfo.ActionType type, Intent intent) {
-        var uri = intent.toUri(Intent.URI_INTENT_SCHEME);
-        var action = EntityInfoProto.ActionInfo.newBuilder()
-                             .setActionType(type)
-                             .setActionUri(uri)
-                             .build();
-
-        return new OmniboxActionInSuggest("wink", action);
+    @Test
+    public void openHistoryClustersPage() {
+        mDelegate.openHistoryClustersPage("query");
+        verify(mMockOpenHistoryClustersUi, times(1)).accept("query");
     }
 
     @Test
-    public void executePedal_manageChromeSettings() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.MANAGE_CHROME_SETTINGS));
-        verify(mMockSettingsLauncher).launchSettingsActivity(any(), eq(SettingsFragment.MAIN));
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_CHROME_SETTINGS);
+    public void openIncognitoTab() {
+        mDelegate.openIncognitoTab();
+        verify(mMockOpenIncognitoPage, times(1)).run();
     }
 
     @Test
-    public void executePedal_clearBrowsingData() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.CLEAR_BROWSING_DATA));
-        verify(mMockSettingsLauncher)
-                .launchSettingsActivity(any(), eq(SettingsFragment.CLEAR_BROWSING_DATA));
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.CLEAR_BROWSING_DATA);
+    public void openPasswordManager() {
+        mDelegate.openPasswordManager();
+        verify(mMockOpenPasswordSettings, times(1)).run();
     }
 
     @Test
-    public void executePedal_managePasswords() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.MANAGE_PASSWORDS));
-        verify(mMockOpenPasswordSettings).run();
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_PASSWORDS);
+    public void openSettingsPage() {
+        mDelegate.openSettingsPage(SettingsFragment.ACCESSIBILITY);
+        verify(mMockSettingsLauncher, times(1))
+                .launchSettingsActivity(mContext, SettingsFragment.ACCESSIBILITY);
     }
 
     @Test
-    public void executePedal_updateCreditCard() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.UPDATE_CREDIT_CARD));
-        verify(mMockSettingsLauncher)
-                .launchSettingsActivity(any(), eq(SettingsFragment.PAYMENT_METHODS));
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.UPDATE_CREDIT_CARD);
-    }
-
-    @Test
-    public void executePedal_runChromeSafetyCheck() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.RUN_CHROME_SAFETY_CHECK));
-        verify(mMockSettingsLauncher)
-                .launchSettingsActivity(any(), eq(SettingsFragment.SAFETY_CHECK));
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.RUN_CHROME_SAFETY_CHECK);
-    }
-
-    @Test
-    public void executePedal_manageSiteSettings() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.MANAGE_SITE_SETTINGS));
-        verify(mMockSettingsLauncher).launchSettingsActivity(any(), eq(SettingsFragment.SITE));
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_SITE_SETTINGS);
-    }
-
-    @Test
-    public void executePedal_manageChromeAccessibility() {
-        mDelegate.execute(buildPedal(OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY));
-        verify(mMockSettingsLauncher)
-                .launchSettingsActivity(any(), eq(SettingsFragment.ACCESSIBILITY));
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY);
-    }
-
-    @Test
-    public void executePedal_launchIncognito() {
-        doReturn(false).when(mTab).isUserInteractable();
-        mDelegate.execute(buildPedal(OmniboxPedalType.LAUNCH_INCOGNITO));
-        verify(mMockOpenIncognitoPage).run();
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.LAUNCH_INCOGNITO);
-    }
-
-    @Test
-    public void executePedal_viewChromeHistory_nonInteractable() {
-        doReturn(false).when(mTab).isUserInteractable();
-        mDelegate.execute(buildPedal(OmniboxPedalType.VIEW_CHROME_HISTORY));
-
-        verify(mMockOpenUrl).accept(UrlConstants.HISTORY_URL);
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.VIEW_CHROME_HISTORY);
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.VIEW_CHROME_HISTORY);
-    }
-
-    @Test
-    public void executePedal_viewChromeHistory() {
-        doReturn(true).when(mTab).isUserInteractable();
-        mDelegate.execute(buildPedal(OmniboxPedalType.VIEW_CHROME_HISTORY));
-
-        var loadParamsCaptor = ArgumentCaptor.forClass(LoadUrlParams.class);
-        verify(mTab, times(1)).loadUrl(loadParamsCaptor.capture());
-
-        var loadUrlParams = loadParamsCaptor.getValue();
-        assertNotNull(loadUrlParams);
-        assertEquals(UrlConstants.HISTORY_URL, loadUrlParams.getUrl());
-    }
-
-    @Test
-    public void executePedal_playChromeDinoGame_nonInteractable() {
-        doReturn(false).when(mTab).isUserInteractable();
-        mDelegate.execute(buildPedal(OmniboxPedalType.PLAY_CHROME_DINO_GAME));
-        verify(mMockOpenUrl).accept(UrlConstants.CHROME_DINO_URL);
-        checkOmniboxPedalUsageRecorded(OmniboxPedalType.PLAY_CHROME_DINO_GAME);
-    }
-
-    @Test
-    public void executeHistoryClusters() {
-        String testJourneyName = "example journey name";
-        mDelegate.execute(buildHistoryClustersAction(testJourneyName));
-        verify(mMockOpenHistoryClustersUi).accept(testJourneyName);
-    }
-
-    @Test
-    public void executeActionInSuggest_executeDirectionsWithMaps() {
-        doReturn(true).when(mTab).isUserInteractable();
-        doReturn(false).when(mTab).isIncognito();
-
-        mDelegate.execute(buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.DIRECTIONS,
-                new Intent("Magic Intent Action")));
-
-        verify(mTab, times(1)).isIncognito();
-        verify(mMockContext, times(1)).startActivity(mIntentCaptor.capture());
-        var intent = mIntentCaptor.getValue();
-
-        assertEquals("Magic Intent Action", intent.getAction());
-
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult",
-                        SuggestionsMetrics.ActionInSuggestIntentResult.SUCCESS));
-        verifyNoMoreInteractions(mTab);
-    }
-
-    @Test
-    public void executeActionInSuggest_executeDirectionsInBrowserForIncognitoMode() {
-        doReturn(true).when(mTab).isUserInteractable();
+    public void isIncognito() {
         doReturn(true).when(mTab).isIncognito();
+        assertTrue(mDelegate.isIncognito());
 
-        var intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
+        doReturn(false).when(mTab).isIncognito();
+        assertFalse(mDelegate.isIncognito());
+    }
 
-        mDelegate.execute(
-                buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.DIRECTIONS, intent));
+    @Test
+    public void loadPageInCurrentTab_useCurrentInteractableTab() {
+        doReturn(true).when(mTab).isUserInteractable();
+        mDelegate.loadPageInCurrentTab("url");
 
         verify(mTab, times(1)).isUserInteractable();
-        verify(mTab, times(1)).isIncognito();
-
-        // Should not be recorded.
-        assertEquals(0,
-                RecordHistogram.getHistogramTotalCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult"));
-
         var loadParamsCaptor = ArgumentCaptor.forClass(LoadUrlParams.class);
         verify(mTab, times(1)).loadUrl(loadParamsCaptor.capture());
-
-        var loadUrlParams = loadParamsCaptor.getValue();
-        assertNotNull(loadUrlParams);
-        assertEquals(UrlConstants.CHROME_DINO_URL, loadUrlParams.getUrl());
+        assertEquals("url", loadParamsCaptor.getValue().getUrl());
         verifyNoMoreInteractions(mTab);
     }
 
     @Test
-    public void executeActionInSuggest_redirectDirectionsActionToLocalTabIfAvailable() {
-        doReturn(true).when(mTab).isUserInteractable();
+    public void loadPageInCurrentTab_openNewTabIfNonInteractable() {
+        doReturn(false).when(mTab).isUserInteractable();
+        mDelegate.loadPageInCurrentTab("url");
 
-        var intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
-
-        // NOTE: the intent is serialized and deserialized. Can't directly check if instance is
-        // same.
-        doThrow(new ActivityNotFoundException()).when(mMockContext).startActivity(any());
-        mDelegate.execute(
-                buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.DIRECTIONS, intent));
         verify(mTab, times(1)).isUserInteractable();
-        verify(mTab, times(1)).isIncognito();
-
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult",
-                        SuggestionsMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND));
-
-        var loadParamsCaptor = ArgumentCaptor.forClass(LoadUrlParams.class);
-        verify(mTab, times(1)).loadUrl(loadParamsCaptor.capture());
-
-        var loadUrlParams = loadParamsCaptor.getValue();
-        assertNotNull(loadUrlParams);
-        assertEquals(UrlConstants.CHROME_DINO_URL, loadUrlParams.getUrl());
         verifyNoMoreInteractions(mTab);
+        verify(mMockOpenUrl).accept("url");
     }
 
     @Test
-    public void executeActionInSuggest_redirectDirectionsActionToRemoteTab() {
-        doReturn(false).when(mTab).isUserInteractable();
-
-        var intent = new Intent(Intent.ACTION_DIAL);
-        intent.setClassName("no.such.package", ".");
-        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
-
-        // First intent is to DIAL with "no.such.package".
-        doThrow(new ActivityNotFoundException())
-                // Confirm the second intent asks to load webpage in Chrome.
-                .doAnswer(inv -> {
-                    Intent newIntent = inv.getArgument(0);
-                    assertEquals(Intent.ACTION_VIEW, newIntent.getAction());
-                    assertEquals(mMockContext.getPackageName(),
-                            newIntent.getComponent().getPackageName());
-                    assertEquals(UrlConstants.CHROME_DINO_URL, newIntent.getDataString());
-                    return 0;
-                })
-                .when(mMockContext)
-                .startActivity(any());
-
-        mDelegate.execute(
-                buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.DIRECTIONS, intent));
-
-        verify(mMockOpenUrl).accept(UrlConstants.CHROME_DINO_URL);
-
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult",
-                        SuggestionsMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND));
+    public void startActivity_targetSelf() {
+        ShadowApplication.getInstance().checkActivities(true);
+        Intent i = new Intent();
+        i.setClass(mContext, TestActivity.class);
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        assertTrue(IntentUtils.intentTargetsSelf(mContext, i));
+        assertTrue(mDelegate.startActivity(i));
+        // Added during intent invocation.
+        assertTrue(i.hasExtra(IntentUtils.TRUSTED_APPLICATION_CODE_EXTRA));
     }
 
     @Test
-    public void executeActionInSuggest_executeCallActionWithDialer() {
-        doReturn(true).when(mTab).isUserInteractable();
-        mDelegate.execute(buildActionInSuggest(
-                EntityInfoProto.ActionInfo.ActionType.CALL, new Intent(Intent.ACTION_CALL)));
-
-        verify(mMockContext, times(1)).startActivity(mIntentCaptor.capture());
-        var intent = mIntentCaptor.getValue();
-        // OBSERVE: We rewrite ACTION_CALL with ACTION_DIAL, which does not carry high permission
-        // requirements.
-        assertEquals(Intent.ACTION_DIAL, intent.getAction());
-
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult",
-                        SuggestionsMetrics.ActionInSuggestIntentResult.SUCCESS));
-        verifyNoMoreInteractions(mTab);
+    public void startActivity_targetOther() {
+        // Do not arm the package resolution.
+        ShadowApplication.getInstance().checkActivities(false);
+        Intent i = new Intent("some magic here");
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        assertFalse(IntentUtils.intentTargetsSelf(mContext, i));
+        assertTrue(mDelegate.startActivity(i));
+        // Might be added during intent invocation.
+        assertFalse(i.hasExtra(IntentUtils.TRUSTED_APPLICATION_CODE_EXTRA));
     }
 
     @Test
-    public void executeActionInSuggest_dontRedirectCallActionToLocalTab() {
-        doReturn(true).when(mTab).isUserInteractable();
-
-        var intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
-
-        // NOTE: the intent is serialized and deserialized. Can't directly check if instance is
-        // same.
-        doThrow(new ActivityNotFoundException()).when(mMockContext).startActivity(any());
-        mDelegate.execute(buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.CALL, intent));
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult",
-                        SuggestionsMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND));
-        verifyNoMoreInteractions(mTab);
-    }
-
-    @Test
-    public void executeActionInSuggest_dontRedirectCallActionToRemoteTab() {
-        doReturn(false).when(mTab).isUserInteractable();
-
-        var intent = new Intent(Intent.ACTION_DIAL);
-        intent.setClassName("no.such.package", ".");
-        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
-
-        // Keep throwing. Test should fail if we attempt to invoke intent to self.
-        doThrow(new ActivityNotFoundException()).when(mMockContext).startActivity(any());
-        mDelegate.execute(buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.CALL, intent));
-
-        verifyNoMoreInteractions(mTab);
-
-        assertEquals(1,
-                RecordHistogram.getHistogramValueCountForTesting(
-                        "Android.Omnibox.ActionInSuggest.IntentResult",
-                        SuggestionsMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND));
+    public void startActivity_failure() {
+        ShadowApplication.getInstance().checkActivities(true);
+        Intent i = new Intent("some magic here");
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        assertFalse(mDelegate.startActivity(i));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePromptTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePromptTest.java
index 980595a..ec9d97d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePromptTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePromptTest.java
@@ -293,6 +293,6 @@
         View dialog = mPrompt.getDialogViewForTesting();
         ImageButton editButton = dialog.findViewById(R.id.edit_button);
         editButton.performClick();
-        verify(mAddressEditor).edit(any());
+        verify(mAddressEditor).showEditorDialog();
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AddressEditorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AddressEditorTest.java
index 9c25538..86e6c772 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AddressEditorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AddressEditorTest.java
@@ -6,6 +6,7 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.isEmptyString;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
@@ -314,7 +315,7 @@
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
         mAddressEditor.setCustomDoneButtonText("Custom done");
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
@@ -329,7 +330,7 @@
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -349,7 +350,7 @@
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -368,11 +369,12 @@
     @Test
     @SmallTest
     public void validateUIStrings_LocalOrSyncAddressProfile_AddressSyncDisabled() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -387,7 +389,8 @@
     @Test
     @SmallTest
     public void validateUIStrings_LocalOrSyncAddressProfile_AddressSyncEnabled() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         when(mSyncService.isSyncFeatureEnabled()).thenReturn(true);
@@ -395,7 +398,7 @@
                 .thenReturn(Collections.singleton(UserSelectableType.AUTOFILL));
 
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -410,11 +413,12 @@
     @Test
     @SmallTest
     public void validateUIStrings_UpdateLocalOrSyncAddressProfile_AddressSyncDisabled() {
-        mAddressEditor =
-                new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false, /*isUpdate=*/true,
-                        /*isMigrationToAccount=*/false);
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
+                /*isUpdate=*/true,
+                /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -429,15 +433,16 @@
     @Test
     @SmallTest
     public void validateUIStrings_UpdateLocalOrSyncAddressProfile_AddressSyncEnabled() {
-        mAddressEditor =
-                new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false, /*isUpdate=*/true,
-                        /*isMigrationToAccount=*/false);
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
+                /*isUpdate=*/true,
+                /*isMigrationToAccount=*/false);
         when(mSyncService.isSyncFeatureEnabled()).thenReturn(true);
         when(mSyncService.getSelectedTypes())
                 .thenReturn(Collections.singleton(UserSelectableType.AUTOFILL));
 
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -452,12 +457,13 @@
     @Test
     @SmallTest
     public void validateUIStrings_LocalAddressProfile_MigrationToAccount() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/true);
 
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -476,7 +482,8 @@
     @Test
     @SmallTest
     public void validateUIStrings_SyncAddressProfile_MigrationToAccount() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/true);
         when(mSyncService.isSyncFeatureEnabled()).thenReturn(true);
@@ -484,7 +491,7 @@
                 .thenReturn(Collections.singleton(UserSelectableType.AUTOFILL));
 
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -503,11 +510,12 @@
     @Test
     @SmallTest
     public void validateUIStrings_AccountAddressProfile_SaveInAccountFlow() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sAccountProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sAccountProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -526,11 +534,12 @@
     @Test
     @SmallTest
     public void validateUIStrings_AccountAddressProfile_UpdateAccountProfileFlow() {
-        mAddressEditor =
-                new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false, /*isUpdate=*/true,
-                        /*isMigrationToAccount=*/false);
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sAccountProfile), /*saveToDisk=*/false,
+                /*isUpdate=*/true,
+                /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sAccountProfile));
+        mAddressEditor.showEditorDialog();
 
         final String deleteTitle =
                 mActivity.getString(R.string.autofill_delete_address_confirmation_dialog_title);
@@ -552,11 +561,12 @@
             ChromeFeatureList.AUTOFILL_ENABLE_SUPPORT_FOR_HONORIFIC_PREFIXES})
     public void
     validateDefaultFields_NicknamesDisabled_HonorificDisabled() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         Assert.assertNotNull(mEditorModelCapture.getValue());
         List<EditorFieldModel> editorFields = mEditorModelCapture.getValue().getFields();
@@ -592,11 +602,12 @@
     @Test
     @SmallTest
     public void validateDefaultFields() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(new ArrayList());
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         Assert.assertNotNull(mEditorModelCapture.getValue());
         List<EditorFieldModel> editorFields = mEditorModelCapture.getValue().getFields();
@@ -620,7 +631,7 @@
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
 
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
         validateShownFields(mEditorModelCapture.getValue(), new AutofillProfile(),
                 /*shouldMarkFieldsRequired=*/false);
     }
@@ -634,7 +645,7 @@
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
 
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
         validateShownFields(mEditorModelCapture.getValue(), new AutofillProfile(),
                 /*shouldMarkFieldsRequired=*/true,
                 /*shouldMarkFieldsRequiredWhenAddressFieldEmpty=*/true);
@@ -644,11 +655,12 @@
     @SmallTest
     public void validateShownFields_LocalOrSyncAddressProfile_SaveLocally() {
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
 
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
         validateShownFields(
                 mEditorModelCapture.getValue(), sLocalProfile, /*shouldMarkFieldsRequired=*/false);
     }
@@ -657,11 +669,12 @@
     @SmallTest
     public void validateShownFields_LocalOrSyncAddressProfile_UpdateLocally() {
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor =
-                new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false, /*isUpdate=*/true,
-                        /*isMigrationToAccount=*/false);
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
+                /*isUpdate=*/true,
+                /*isMigrationToAccount=*/false);
 
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
         validateShownFields(
                 mEditorModelCapture.getValue(), sLocalProfile, /*shouldMarkFieldsRequired=*/false);
     }
@@ -670,11 +683,12 @@
     @SmallTest
     public void validateShownFields_LocalOrSyncAddressProfile_MigrationToAccount() {
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/true);
 
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
         validateShownFields(
                 mEditorModelCapture.getValue(), sLocalProfile, /*shouldMarkFieldsRequired=*/true);
     }
@@ -683,11 +697,12 @@
     @SmallTest
     public void validateShownFields_AccountProfile_SaveInAccountFlow() {
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sAccountProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
 
-        mAddressEditor.edit(new AutofillAddress(mActivity, sAccountProfile));
+        mAddressEditor.showEditorDialog();
         validateShownFields(
                 mEditorModelCapture.getValue(), sAccountProfile, /*shouldMarkFieldsRequired=*/true);
     }
@@ -696,11 +711,12 @@
     @SmallTest
     public void validateShownFields_AccountProfile_UpdateAlreadySaved() {
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor =
-                new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false, /*isUpdate=*/true,
-                        /*isMigrationToAccount=*/false);
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sAccountProfile), /*saveToDisk=*/false,
+                /*isUpdate=*/true,
+                /*isMigrationToAccount=*/false);
 
-        mAddressEditor.edit(new AutofillAddress(mActivity, sAccountProfile));
+        mAddressEditor.showEditorDialog();
         validateShownFields(
                 mEditorModelCapture.getValue(), sAccountProfile, /*shouldMarkFieldsRequired=*/true);
     }
@@ -708,7 +724,8 @@
     @Test
     @SmallTest
     public void edit_ChangeCountry_FieldsSetChanges() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, sLocalProfile), /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(List.of(new AddressUiComponent(AddressField.SORTING_CODE,
@@ -717,7 +734,7 @@
         setUpAddressUiComponents(List.of(new AddressUiComponent(AddressField.STREET_ADDRESS,
                                          "street address label", true, true)),
                 "DE");
-        mAddressEditor.edit(new AutofillAddress(mActivity, sLocalProfile));
+        mAddressEditor.showEditorDialog();
 
         Assert.assertNotNull(mEditorModelCapture.getValue());
         List<EditorFieldModel> editorFields = mEditorModelCapture.getValue().getFields();
@@ -763,7 +780,7 @@
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
@@ -788,12 +805,13 @@
     @Test
     @SmallTest
     public void edit_AlterAddressProfile_Cancel() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, new AutofillProfile(sLocalProfile)),
+                /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
-        AutofillProfile toEdit = new AutofillProfile(sLocalProfile);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor.edit(new AutofillAddress(mActivity, toEdit));
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
@@ -813,12 +831,13 @@
     @Test
     @SmallTest
     public void edit_AlterAddressProfile_CommitChanges() {
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, new AutofillProfile(sLocalProfile)),
+                /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
-        AutofillProfile toEdit = new AutofillProfile(sLocalProfile);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        mAddressEditor.edit(new AutofillAddress(mActivity, toEdit));
+        mAddressEditor.showEditorDialog();
 
         Assert.assertNotNull(mEditorModelCapture.getValue());
         EditorModel editorModel = mEditorModelCapture.getValue();
@@ -842,6 +861,46 @@
 
     @Test
     @SmallTest
+    public void edit_AlterAddressProfile_CommitChanges_InvisibleFieldsGetReset() {
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, new AutofillProfile(sLocalProfile)),
+                /*saveToDisk=*/false,
+                /*isUpdate=*/false,
+                /*isMigrationToAccount=*/false);
+
+        // Whitelist only full name, admin area and locality.
+        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS.subList(0, 3));
+        mAddressEditor.showEditorDialog();
+
+        EditorModel editorModel = mEditorModelCapture.getValue();
+        Assert.assertNotNull(editorModel);
+        List<EditorFieldModel> editorFields = editorModel.getFields();
+        // editorFields[0] - country dropdown.
+        // editorFields[1] - honorific prefix field.
+        // editorFields[2] - full name field.
+        // editorFields[3] - admin area field.
+        // editorFields[4] - locality field.
+        // editorFields[5] - phone number field.
+        // editorFields[6] - email field.
+        // editorFields[7] - nickname field.
+        Assert.assertEquals(8, editorFields.size());
+
+        editorModel.done();
+        verify(mDelegate, times(1)).onDone(mAddressCapture.capture());
+        verify(mDelegate, times(0)).onCancel();
+
+        AutofillAddress address = mAddressCapture.getValue();
+        Assert.assertNotNull(address);
+        AutofillProfile profile = address.getProfile();
+        assertThat(profile.getStreetAddress(), isEmptyString());
+        assertThat(profile.getDependentLocality(), isEmptyString());
+        assertThat(profile.getCompanyName(), isEmptyString());
+        assertThat(profile.getPostalCode(), isEmptyString());
+        assertThat(profile.getSortingCode(), isEmptyString());
+    }
+
+    @Test
+    @SmallTest
     public void accountSavingDisallowedForUnsupportedCountry() {
         when(mPersonalDataManager.isEligibleForAddressAccountStorage()).thenReturn(true);
         when(mPersonalDataManager.isCountryEligibleForAccountStorage(eq("CU"))).thenReturn(false);
@@ -850,7 +909,7 @@
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, "US");
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, "CU");
-        mAddressEditor.edit(null);
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
@@ -879,12 +938,13 @@
     @SmallTest
     public void countryDropDownExcludesUnsupportedCountries_saveInAccountFlow() {
         when(mPersonalDataManager.isCountryEligibleForAccountStorage(eq("CU"))).thenReturn(false);
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, new AutofillProfile(sAccountProfile)),
+                /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        AutofillProfile toEdit = new AutofillProfile(sAccountProfile);
-        mAddressEditor.edit(new AutofillAddress(mActivity, toEdit));
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
@@ -898,12 +958,13 @@
     @SmallTest
     public void countryDropDownExcludesUnsupportedCountries_MigrationFlow() {
         when(mPersonalDataManager.isCountryEligibleForAccountStorage(eq("CU"))).thenReturn(false);
-        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false,
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, new AutofillProfile(sLocalProfile)),
+                /*saveToDisk=*/false,
                 /*isUpdate=*/false,
                 /*isMigrationToAccount=*/true);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        AutofillProfile toEdit = new AutofillProfile(sLocalProfile);
-        mAddressEditor.edit(new AutofillAddress(mActivity, toEdit));
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
@@ -917,12 +978,12 @@
     @SmallTest
     public void countryDropDownExcludesUnsupportedCountries_editExistingAccountProfile() {
         when(mPersonalDataManager.isCountryEligibleForAccountStorage(eq("CU"))).thenReturn(false);
-        mAddressEditor =
-                new AddressEditor(mEditorDialog, mDelegate, /*saveToDisk=*/false, /*isUpdate=*/true,
-                        /*isMigrationToAccount=*/false);
+        mAddressEditor = new AddressEditor(mEditorDialog, mDelegate,
+                new AutofillAddress(mActivity, new AutofillProfile(sAccountProfile)),
+                /*saveToDisk=*/false, /*isUpdate=*/true,
+                /*isMigrationToAccount=*/false);
         setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS);
-        AutofillProfile toEdit = new AutofillProfile(sAccountProfile);
-        mAddressEditor.edit(new AutofillAddress(mActivity, toEdit));
+        mAddressEditor.showEditorDialog();
 
         EditorModel editorModel = mEditorModelCapture.getValue();
         Assert.assertNotNull(editorModel);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinatorTest.java
index 45508810..cea6591 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinatorTest.java
@@ -48,7 +48,7 @@
 @Config(manifest = Config.NONE)
 @CommandLineFlags.
 Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ChromeSwitches.DISABLE_NATIVE_INITIALIZATION})
-@Features.EnableFeatures({ChromeFeatureList.BOOKMARKS_REFRESH})
+@Features.EnableFeatures({ChromeFeatureList.BOOKMARKS_REFRESH, ChromeFeatureList.EMPTY_STATES})
 public class BookmarkManagerCoordinatorTest {
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -130,4 +130,4 @@
         assertNotNull(mCoordinator.buildAndInitCompactImprovedBookmarkRow(parent));
         assertNotNull(mCoordinator.buildAndInitVisualImprovedBookmarkRow(parent));
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
index fa3fc93..349a40b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
@@ -103,7 +103,8 @@
 @Batch(Batch.UNIT_TESTS)
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@Features.EnableFeatures({ChromeFeatureList.BOOKMARKS_REFRESH, ChromeFeatureList.SHOPPING_LIST})
+@Features.EnableFeatures({ChromeFeatureList.BOOKMARKS_REFRESH, ChromeFeatureList.SHOPPING_LIST,
+        ChromeFeatureList.EMPTY_STATES})
 public class BookmarkManagerMediatorTest {
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -335,6 +336,60 @@
     }
 
     @Test
+    @Features.DisableFeatures({ChromeFeatureList.EMPTY_STATES})
+    public void testEmptyView_Bookmark() {
+        // Setup and open Bookmark folder.
+        finishLoading();
+        assertEquals(BookmarkUiMode.LOADING, mMediator.getCurrentUiMode());
+        mMediator.openFolder(mFolderId1);
+
+        // Verify empty view initialized.
+        verify(mSelectableListLayout).setEmptyViewText(R.string.bookmarks_folder_empty);
+    }
+
+    @Test
+    @Features.DisableFeatures({ChromeFeatureList.EMPTY_STATES})
+    public void testEmptyView_ReadingList() {
+        // Setup and open Reading list folder.
+        finishLoading();
+        assertEquals(BookmarkUiMode.LOADING, mMediator.getCurrentUiMode());
+        mMediator.openFolder(mReadingListFolderId);
+
+        // Verify empty view initialized.
+        verify(mSelectableListLayout).setEmptyViewText(R.string.reading_list_empty_list_title);
+    }
+
+    @Test
+    public void testEmptyView_EmptyState_Bookmark() {
+        // Setup and open Bookmark folder.
+        finishLoading();
+        assertEquals(BookmarkUiMode.LOADING, mMediator.getCurrentUiMode());
+        mMediator.openFolder(mFolderId1);
+
+        // Verify empty view initialized.
+        verify(mSelectableListLayout)
+                .setEmptyStateImageRes(R.drawable.bookmark_empty_state_illustration);
+        verify(mSelectableListLayout)
+                .setEmptyStateViewText(R.string.bookmark_manager_empty_state,
+                        R.string.bookmark_manager_back_to_page_by_adding_bookmark);
+    }
+
+    @Test
+    public void testEmptyView_EmptyState_ReadingList() {
+        // Setup and open Reading list folder.
+        finishLoading();
+        assertEquals(BookmarkUiMode.LOADING, mMediator.getCurrentUiMode());
+        mMediator.openFolder(mReadingListFolderId);
+
+        // Verify empty view initialized.
+        verify(mSelectableListLayout)
+                .setEmptyStateImageRes(R.drawable.reading_list_empty_state_illustration);
+        verify(mSelectableListLayout)
+                .setEmptyStateViewText(R.string.reading_list_manager_empty_state,
+                        R.string.reading_list_manager_save_page_to_read_later);
+    }
+
+    @Test
     public void syncStateChangedBeforeModelLoaded() {
         verify(mSyncService, atLeast(1))
                 .addSyncStateChangedListener(mSyncStateChangedListenerCaptor.capture());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandlerTest.java
index d3320650..3db975e6 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandlerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/LegacyBookmarkQueryHandlerTest.java
@@ -34,13 +34,13 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.chrome.browser.bookmarks.BookmarkListEntry.ViewType;
+import org.chromium.chrome.browser.commerce.ShoppingFeatures;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.sync.SyncService;
 import org.chromium.chrome.browser.sync.SyncService.SyncStateChangedListener;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.feature_engagement.Tracker;
 
@@ -52,7 +52,6 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
-@DisableFeatures(ChromeFeatureList.SHOPPING_LIST)
 public class LegacyBookmarkQueryHandlerTest {
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -83,6 +82,7 @@
         TrackerFactory.setTrackerForTests(mTracker);
         Profile.setLastUsedProfileForTesting(mProfile);
         SharedBookmarkModelMocks.initMocks(mBookmarkModel);
+        ShoppingFeatures.setShoppingListEligibleForTesting(false);
 
         mHandler = new LegacyBookmarkQueryHandler(mBookmarkModel, mBookmarkUiPrefs);
     }
@@ -128,8 +128,8 @@
     }
 
     @Test
-    @EnableFeatures(ChromeFeatureList.SHOPPING_LIST)
     public void testBuildBookmarkListForParent_rootFolder_withShopping() {
+        ShoppingFeatures.setShoppingListEligibleForTesting(true);
         verify(mBookmarkModel)
                 .finishLoadingBookmarkModel(mFinishLoadingBookmarkModelCaptor.capture());
         doReturn(true).when(mBookmarkModel).isBookmarkModelLoaded();
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index fb50937..c5a7618 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -30,6 +30,7 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/thread_controller.h"
 #include "base/task/sequence_manager/thread_controller_power_monitor.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/threading/hang_watcher.h"
@@ -956,6 +957,7 @@
 
   base::InitializeCpuReductionExperiment();
   base::sequence_manager::internal::SequenceManagerImpl::InitializeFeatures();
+  base::sequence_manager::internal::ThreadController::InitializeFeatures();
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
   base::MessagePumpLibevent::InitializeFeatures();
 #elif BUILDFLAG(IS_MAC)
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index d872974..10db077 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -4467,6 +4467,9 @@
   <message name="IDS_PRINT_JOB_ERROR_NOTIFICATION_TITLE" desc="Title of the error printing notification.">
     Couldn't print. Check printer and try again.
   </message>
+  <message name="IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE" desc="Title of the expired certificate error printing notification">
+    Printer SSL certificate is expired. Restart printer and try again.
+  </message>
   <message name="IDS_PRINT_JOB_AUTHORIZATION_ERROR_NOTIFICATION_TITLE" desc="Title of the authorization error printing notification.">
     Authorization Failed
   </message>
@@ -6054,15 +6057,12 @@
   <message name="IDS_BOREALIS_INSTALLER_ONGOING_MESSAGE" desc="Body for Steam (noun, name of app) installer while Steam is installing.">
     This may take a few minutes
   </message>
+  <message name="IDS_BOREALIS_INSTALLER_ONGOING_PERCENTAGE" desc="Message containing the progress percentage of the ongoing Steam (noun, name of app) installation.">
+    <ph name="PERCENTAGE_COMPLETE">$1<ex>42</ex></ph>% completed
+  </message>
   <message name="IDS_BOREALIS_INSTALLER_ONGOING_INACTIVE" desc="Message shown while the steam installer is initializing.">
     Starting installation
   </message>
-  <message name="IDS_BOREALIS_INSTALLER_ONGOING_DLC" desc="Message shown while the steam installer is downloading its content.">
-    Downloading
-  </message>
-  <message name="IDS_BOREALIS_INSTALLER_ONGOING_DRYRUN" desc="Message shown while the steam installer is performing a post-installation trial.">
-    Performing setup
-  </message>
   <message name="IDS_BOREALIS_INSTALLER_FINISHED_TITLE" desc="Title for installer when installation for Steam (noun, name of app) completes succesfully.">
     You’re all set!
   </message>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_DLC.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_DLC.png.sha1
deleted file mode 100644
index 2962b20..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_DLC.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-190e7c6b2f3e4a7d381ac7b07aac087b607a3a08
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_DRYRUN.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_DRYRUN.png.sha1
deleted file mode 100644
index 6e21f0b..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_DRYRUN.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-0d533946a738fe778d6f789a5748ad9159e12a4a
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_PERCENTAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_PERCENTAGE.png.sha1
new file mode 100644
index 0000000..d0d3c9c
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_BOREALIS_INSTALLER_ONGOING_PERCENTAGE.png.sha1
@@ -0,0 +1 @@
+c63aff898189c0eec5b7c7d88e9f8f281faf4134
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE.png.sha1
new file mode 100644
index 0000000..6e151ef4
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@
+794ed9f095125c996a9c59d3d3440b60fa516f18
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index badd84b0..ddbe7ca 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1144,8 +1144,8 @@
           <message name="IDS_ADDRESSES_AND_MORE_SUBMENU_OPTION" desc="The text label of the addresses and more item for the autofill submenu">
             &amp;Addresses and more
           </message>
-          <message name="IDS_AUTOFILL_MENU" desc="The text label of the autofill menu item">
-            &amp;Autofill and passwords
+          <message name="IDS_PASSWORDS_AND_AUTOFILL_MENU" desc="The text label of the passwords and autofill menu item">
+            Passwords and &amp;autofill
           </message>
           <message name="IDS_NEW_TAB" desc="The text label of a menu item for opening a new tab">
             New &amp;tab
@@ -1245,8 +1245,8 @@
           <message name="IDS_ADDRESSES_AND_MORE_SUBMENU_OPTION" desc="In Title Case: The text label of the addresses and more item for the autofill submenu">
             &amp;Addresses and More
           </message>
-          <message name="IDS_AUTOFILL_MENU" desc="In Title Case: The text label of the autofill menu item">
-            &amp;Autofill and Passwords
+          <message name="IDS_PASSWORDS_AND_AUTOFILL_MENU" desc="In Title Case: The text label of the passwords and autofill menu item">
+            Passwords and &amp;Autofill
           </message>
           <message name="IDS_NEW_TAB" desc="In Title Case: The text label of a menu item for opening a new tab">
             New &amp;Tab
@@ -2666,6 +2666,10 @@
                desc="Malware warning for the user.">
         Malware
       </message>
+      <message name="IDS_DOWNLOAD_BUBBLE_STATUS_SUSPICIOUS"
+        desc="Let the user know the download is suspicious and may need additional safety checks">
+        Suspicious
+      </message>
       <message name="IDS_DOWNLOAD_BUBBLE_CHECKBOX_BYPASS"
                desc="The label on the checkbox with which the user chooses to bypass blocked download.">
         I understand this download will harm my computer
@@ -2836,7 +2840,7 @@
       </message>
       <message name="IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT"
                desc="Prompt for Deep Scanning of a download">
-        Scan before opening
+        Scan for malware
       </message>
       <message name="IDS_DOWNLOAD_BUBBLE_STATUS_ASYNC_SCANNING"
                desc="Status text for a download item that is being scanned.">
@@ -5228,7 +5232,7 @@
           Allow <ph name="EXTENSIONS_REQUESTING_ACCESS_COUNT">$1<ex>3</ex></ph>?
         </message>
         <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_SINGLE_EXTENSION" desc="The tooltip text of the request access button that appears on the toolbar when an extension requests access to the site">
-          Click to allow "<ph name="EXTENSIONS_REQUESTING_ACCESS">$1<ex>Extension A</ex></ph>" on <ph name="ORIGIN">$2<ex>google.com</ex></ph>:
+          Click to allow "<ph name="EXTENSIONS_REQUESTING_ACCESS">$1<ex>Extension A</ex></ph>" on <ph name="ORIGIN">$2<ex>google.com</ex></ph>
         </message>
         <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_MULTIPLE_EXTENSIONS" desc="The tooltip text of the request access button that appears on the toolbar when an extension requests access to the site">
           Click to allow on <ph name="ORIGIN">$1<ex>google.com</ex></ph>:
@@ -12701,6 +12705,9 @@
       <message name="IDS_SHARE_THIS_TAB_DIALOG_ALLOW" desc="Used for the allow button on the tab-sharing dialog shown when a getDisplayMedia capture is constrained to share only the current tab.">
         Allow
       </message>
+      <message name="IDS_SHARE_THIS_TAB_AUDIO_SHARE" desc="Text for the audio toggle on the tab-sharing dialog. If the toggle is enabled, tab audio will be shared; otherwise only video.">
+        Also allow tab audio
+      </message>
 
 
       <message name="IDS_TAB_CAPTURE_TERMINATED_BY_POLICY_TITLE" desc="Title for a confirmation dialog shown on the captured tab when capture of that tab is terminated due to enterprise policy.">
diff --git a/chrome/app/generated_resources_grd/IDS_AUTOFILL_MENU.png.sha1 b/chrome/app/generated_resources_grd/IDS_AUTOFILL_MENU.png.sha1
deleted file mode 100644
index 2498321b..0000000
--- a/chrome/app/generated_resources_grd/IDS_AUTOFILL_MENU.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-37e6256196fb921f35c48e5afcbf3ef1d7f18dce
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT.png.sha1
index 812ac00..bf4fb84a 100644
--- a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT.png.sha1
@@ -1 +1 @@
-c34f9be456aad5fc47f3b5d180297721866f8b70
\ No newline at end of file
+2e883effa31f7cde66da4f4d3681b882c57dcb8c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_SUSPICIOUS.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_SUSPICIOUS.png.sha1
new file mode 100644
index 0000000..bf4fb84a
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_STATUS_SUSPICIOUS.png.sha1
@@ -0,0 +1 @@
+2e883effa31f7cde66da4f4d3681b882c57dcb8c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_SINGLE_EXTENSION.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_SINGLE_EXTENSION.png.sha1
index 9411a23..61f13001 100644
--- a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_SINGLE_EXTENSION.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_SINGLE_EXTENSION.png.sha1
@@ -1 +1 @@
-2492098bd6bb7b77f4a4d6af1fb036b38ac99732
\ No newline at end of file
+7f5e663b63df8b321bf9b32c3803c9ba0b4b2c16
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORDS_AND_AUTOFILL_MENU.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORDS_AND_AUTOFILL_MENU.png.sha1
new file mode 100644
index 0000000..e5f34e7
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORDS_AND_AUTOFILL_MENU.png.sha1
@@ -0,0 +1 @@
+7cb4bbf9e1d007c4e30fb22cd9f8da5d222a1a98
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SHARE_THIS_TAB_AUDIO_SHARE.png.sha1 b/chrome/app/generated_resources_grd/IDS_SHARE_THIS_TAB_AUDIO_SHARE.png.sha1
new file mode 100644
index 0000000..dc30f047
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SHARE_THIS_TAB_AUDIO_SHARE.png.sha1
@@ -0,0 +1 @@
+8074f0040f79cc90a2755aec6a33436897efb1e5
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index ddcbbea..941bac3f 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -4915,8 +4915,8 @@
   <message name="IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_COMMAND" desc="In keyboard remap keys subpage, the dropdown list item for the external Command key on Apple keyboards when there's no internal keyboard on the device.">
     command
   </message>
-  <message name="IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_EXTERNAL_META" desc="In keyboard remap keys subpage, the dropdown list item for the external Meta key (Search key on ChromeOS keyboards, and Windows key on Windows keyboards).">
-    external meta
+  <message name="IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_META" desc="In keyboard remap keys subpage, the dropdown list item for the Meta key (Search key on ChromeOS keyboards, and Windows key on Windows keyboards).">
+    meta
   </message>
   <message name="IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_SEARCH" desc="In keyboard remap keys subpage, the label and dropdown list item for the Search key.">
     search
@@ -5282,6 +5282,12 @@
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_HW_MICROPHONE_TOGGLE_TOOLTIP" desc="Tooltip to display in the Privacy Hub subpage when the hardware microphone switch is engaged.">
     To turn on microphone access, turn on the physical microphone switch on your device
   </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_TITLE" desc="The title of the toggle to enable/disable microphone from the privacy hub.">
+    Mute nudge
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_SUBTEXT" desc="Sub-label for the microphone access toggle in the Privacy controls (aka Privacy Hub) os-settings page.">
+    You'll get notified if you talk while your mic is muted when using certain apps, like video chat apps. Audio never leaves your device.
+  </message>
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_TOGGLE_TITLE" desc="The title of the toggle to enable/disable geolocation from the privacy hub.">
     Location access
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_SUBTEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_SUBTEXT.png.sha1
new file mode 100644
index 0000000..f8214f8
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_SUBTEXT.png.sha1
@@ -0,0 +1 @@
+569855b265648375594d212e265d5253c3090a67
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_TITLE.png.sha1
new file mode 100644
index 0000000..f8214f8
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_TITLE.png.sha1
@@ -0,0 +1 @@
+569855b265648375594d212e265d5253c3090a67
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_EXTERNAL_META.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_EXTERNAL_META.png.sha1
deleted file mode 100644
index a082dd9..0000000
--- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_EXTERNAL_META.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-10e6cc2dd102fc248eea9d46dae9a7ad90f8c9ed
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_META.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_META.png.sha1
new file mode 100644
index 0000000..0db7365
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_META.png.sha1
@@ -0,0 +1 @@
+2a6158ec5f8410f18b87b38e4f8e1be6d0a8fe27
\ No newline at end of file
diff --git a/chrome/app/theme/borealis/logo_borealis_flair_64.png b/chrome/app/theme/borealis/logo_borealis_flair_64.png
new file mode 100644
index 0000000..a5841af
--- /dev/null
+++ b/chrome/app/theme/borealis/logo_borealis_flair_64.png
Binary files differ
diff --git a/chrome/app/theme/chrome_unscaled_resources.grd b/chrome/app/theme/chrome_unscaled_resources.grd
index 2dca7d6..145e170 100644
--- a/chrome/app/theme/chrome_unscaled_resources.grd
+++ b/chrome/app/theme/chrome_unscaled_resources.grd
@@ -151,6 +151,7 @@
         <include name="IDR_LOGO_BOREALIS_DEFAULT_192" file="borealis/logo_borealis_default_192.png" type="BINDATA" />
         <include name="IDR_LOGO_BOREALIS_STEAM_192" file="borealis/logo_borealis_steam_192.png" type="BINDATA" />
         <include name="IDR_LOGO_BOREALIS_SPLASH" file="borealis/logo_borealis_splash.png" type="BINDATA" />
+        <include name="IDR_LOGO_BOREALIS_FLAIR" file="borealis/logo_borealis_flair_64.png" type="BINDATA" />
         <!-- TODO(b/248938308): Replace the below large .png files with svg/lottie when we update to WebUI. -->
         <include name="IDR_BOREALIS_INSTALLER_COMPLETE_LIGHT" file="borealis/borealis_installer_complete_light.png" type="BINDATA" />
         <include name="IDR_BOREALIS_INSTALLER_COMPLETE_DARK" file="borealis/borealis_installer_complete_dark.png" type="BINDATA" />
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index b900896..d9e919c 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -487,6 +487,8 @@
     "enterprise/connectors/interstitials/enterprise_warn_controller_client.h",
     "enterprise/connectors/interstitials/enterprise_warn_page.cc",
     "enterprise/connectors/interstitials/enterprise_warn_page.h",
+    "enterprise/profile_management/saml_response_parser.cc",
+    "enterprise/profile_management/saml_response_parser.h",
     "enterprise/reporting/legacy_tech/legacy_tech_report_policy_handler.cc",
     "enterprise/reporting/legacy_tech/legacy_tech_report_policy_handler.h",
     "enterprise/reporting/legacy_tech/legacy_tech_url_matcher.cc",
@@ -4415,7 +4417,7 @@
       "//chrome/common/importer:interfaces",
       "//chrome/common/themes:autogenerated_theme_util",
       "//chrome/services/media_gallery_util/public/cpp",
-      "//components/access_code_cast/common:common",
+      "//components/access_code_cast/common:metrics",
       "//components/app_constants",
       "//components/commerce/core:cart_db_content_proto",
       "//components/commerce/core:coupon_db_content_proto",
@@ -4768,6 +4770,14 @@
       "metrics/perf/windowed_incognito_observer.h",
       "metrics/profile_pref_names.cc",
       "metrics/profile_pref_names.h",
+      "metrics/structured/ash_structured_metrics_recorder.cc",
+      "metrics/structured/ash_structured_metrics_recorder.h",
+      "metrics/structured/cros_events_processor.cc",
+      "metrics/structured/cros_events_processor.h",
+      "metrics/structured/metadata_processor_ash.cc",
+      "metrics/structured/metadata_processor_ash.h",
+      "metrics/structured/structured_metrics_key_events_observer.cc",
+      "metrics/structured/structured_metrics_key_events_observer.h",
       "metrics/update_engine_metrics_provider.cc",
       "metrics/update_engine_metrics_provider.h",
       "metrics/usertype_by_devicetype_metrics_provider.cc",
@@ -5423,6 +5433,8 @@
       "metrics/enrollment_status.h",
       "metrics/lacros_metrics_provider.cc",
       "metrics/lacros_metrics_provider.h",
+      "metrics/structured/lacros_structured_metrics_recorder.cc",
+      "metrics/structured/lacros_structured_metrics_recorder.h",
       "notifications/notification_platform_bridge_chromeos.cc",
       "notifications/notification_platform_bridge_chromeos.h",
       "notifications/notification_platform_bridge_delegate.h",
@@ -5538,6 +5550,8 @@
       "media/platform_verification_chromeos.h",
       "memory/oom_kills_monitor.cc",
       "memory/oom_kills_monitor.h",
+      "metrics/structured/chrome_structured_metrics_recorder.cc",
+      "metrics/structured/chrome_structured_metrics_recorder.h",
       "notifications/passphrase_textfield.cc",
       "notifications/passphrase_textfield.h",
       "obsolete_system/obsolete_system_stub.cc",
@@ -5561,7 +5575,6 @@
       "//chrome/browser/chromeos",
       "//chrome/browser/chromeos/drivefs",
       "//chrome/browser/chromeos/extensions/telemetry",
-      "//chrome/browser/metrics/structured",
       "//chrome/browser/policy:onc",
       "//chrome/browser/ui/quick_answers",
       "//chrome/browser/ui/webui/settings/chromeos/constants:mojom",
@@ -5587,6 +5600,8 @@
       "//components/app_constants",
       "//components/arc/common",
       "//components/arc/common:arc_intent_helper_constants",
+      "//components/metrics/structured",
+      "//components/metrics/structured:structured_events",
       "//components/pref_registry:pref_registry",
       "//components/prefs:prefs",
       "//components/reporting/metrics:metrics_data_collection",
@@ -6128,19 +6143,21 @@
       "enterprise/connectors/analysis/local_binary_upload_service.h",
       "enterprise/connectors/analysis/local_binary_upload_service_factory.cc",
       "enterprise/connectors/analysis/local_binary_upload_service_factory.h",
+      "enterprise/connectors/device_trust/attestation/browser/attestation_switches.cc",
+      "enterprise/connectors/device_trust/attestation/browser/attestation_switches.h",
       "enterprise/connectors/device_trust/attestation/browser/attester.h",
+      "enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.cc",
+      "enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.h",
+      "enterprise/connectors/device_trust/attestation/browser/crypto_utility.cc",
+      "enterprise/connectors/device_trust/attestation/browser/crypto_utility.h",
       "enterprise/connectors/device_trust/attestation/browser/device_attester.cc",
       "enterprise/connectors/device_trust/attestation/browser/device_attester.h",
+      "enterprise/connectors/device_trust/attestation/browser/google_keys.cc",
+      "enterprise/connectors/device_trust/attestation/browser/google_keys.h",
       "enterprise/connectors/device_trust/attestation/browser/profile_attester.cc",
       "enterprise/connectors/device_trust/attestation/browser/profile_attester.h",
-      "enterprise/connectors/device_trust/attestation/desktop/crypto_utility.cc",
-      "enterprise/connectors/device_trust/attestation/desktop/crypto_utility.h",
       "enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.cc",
       "enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.h",
-      "enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.cc",
-      "enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h",
-      "enterprise/connectors/device_trust/attestation/desktop/google_keys.cc",
-      "enterprise/connectors/device_trust/attestation/desktop/google_keys.h",
       "enterprise/connectors/device_trust/browser/signing_key_policy_observer.cc",
       "enterprise/connectors/device_trust/browser/signing_key_policy_observer.h",
       "enterprise/connectors/device_trust/signals/decorators/browser/browser_signals_decorator.cc",
@@ -6794,14 +6811,10 @@
       "signin/bound_session_credentials/bound_session_cookie_refresh_service_impl.cc",
       "signin/bound_session_credentials/bound_session_cookie_refresh_service_impl.h",
       "signin/bound_session_credentials/bound_session_refresh_cookie_fetcher.h",
+      "signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.cc",
+      "signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h",
       "signin/bound_session_credentials/bound_session_request_throttled_listener_browser_impl.cc",
       "signin/bound_session_credentials/bound_session_request_throttled_listener_browser_impl.h",
-
-      # TODO(b/273920907): Move`fake_bound_session_refresh_cookie_fetcher.*`
-      # to test only once`bound_session_refresh_cookie_fetcher_impl.*` is
-      # implemented.
-      "signin/bound_session_credentials/fake_bound_session_refresh_cookie_fetcher.cc",
-      "signin/bound_session_credentials/fake_bound_session_refresh_cookie_fetcher.h",
       "signin/bound_session_credentials/registration_token_helper.cc",
       "signin/bound_session_credentials/registration_token_helper.h",
       "signin/bound_session_credentials/unexportable_key_service_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0e32f3b1..e7fff932 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3100,16 +3100,30 @@
 const FeatureEntry::FeatureParam kTabStripRedesignDisableNtbAnchorDetached[] = {
     {"disable_ntb_anchor", "true"},
     {"enable_detached", "true"}};
+const FeatureEntry::FeatureParam
+    kTabStripRedesignDisableToolbarReorderingFolio[] = {
+        {"disable_toolbar_reordering", "true"},
+        {"enable_folio", "true"}};
+const FeatureEntry::FeatureParam
+    kTabStripRedesignDisableToolbarReorderingDetached[] = {
+        {"disable_toolbar_reordering", "true"},
+        {"enable_detached", "true"}};
 
 const FeatureEntry::FeatureVariation kTabStripRedesignVariations[] = {
     {"Folio", kTabStripRedesignFolio, std::size(kTabStripRedesignFolio),
      nullptr},
     {"Detached", kTabStripRedesignDetached,
      std::size(kTabStripRedesignDetached), nullptr},
-    {"Folio NTB Fixed Position", kTabStripRedesignDisableNtbAnchorFolio,
+    {"Folio NTB Unanchored ", kTabStripRedesignDisableNtbAnchorFolio,
      std::size(kTabStripRedesignDisableNtbAnchorFolio), nullptr},
-    {"Detached NTB Fixed Position", kTabStripRedesignDisableNtbAnchorDetached,
-     std::size(kTabStripRedesignDisableNtbAnchorDetached), nullptr}};
+    {"Detached NTB Unanchored", kTabStripRedesignDisableNtbAnchorDetached,
+     std::size(kTabStripRedesignDisableNtbAnchorDetached), nullptr},
+    {"Folio Shut off Toolbar Reordering ",
+     kTabStripRedesignDisableToolbarReorderingFolio,
+     std::size(kTabStripRedesignDisableToolbarReorderingFolio), nullptr},
+    {"Detached Shut off Toolbar Reordering",
+     kTabStripRedesignDisableToolbarReorderingDetached,
+     std::size(kTabStripRedesignDisableToolbarReorderingDetached), nullptr}};
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
@@ -3362,6 +3376,28 @@
 
 };
 
+const FeatureEntry::FeatureParam kMemorySavingsReportingFrequent[] = {
+    // 100 * 1024 * 1024
+    {"expanded_high_efficiency_chip_threshold_bytes", "104857600"},
+    {"expanded_high_efficiency_chip_frequency", "2h"},
+    {"expanded_high_efficiency_chip_discarded_duration", "1h"}};
+
+const FeatureEntry::FeatureParam kMemorySavingsReportingInfrequent[] = {
+    // 200 * 1024 * 1024
+    {"expanded_high_efficiency_chip_threshold_bytes", "209715200"},
+    {"expanded_high_efficiency_chip_frequency", "1d"},
+    {"expanded_high_efficiency_chip_discarded_duration", "6h"}};
+
+const FeatureEntry::FeatureVariation
+    kHighEfficiencyMemorySavingsReportingVariations[] = {
+        {"With Frequent Memory Savings Reporting",
+         kMemorySavingsReportingFrequent,
+         std::size(kMemorySavingsReportingFrequent), nullptr},
+        {"With Infrequent Memory Savings Reporting",
+         kMemorySavingsReportingInfrequent,
+         std::size(kMemorySavingsReportingInfrequent), nullptr},
+};
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -5315,6 +5351,10 @@
      flag_descriptions::kFillOnAccountSelectDescription, kOsAll,
      FEATURE_VALUE_TYPE(password_manager::features::kFillOnAccountSelect)},
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+    {"arc-aaudio-mmap-low-latency",
+     flag_descriptions::kArcAAudioMMAPLowLatencyName,
+     flag_descriptions::kArcAAudioMMAPLowLatencyDescription, kOsCrOS,
+     PLATFORM_FEATURE_NAME_TYPE("CrOSLateBootArcVmAAudioMMAPLowLatency")},
     {"arc-custom-tabs-experiment",
      flag_descriptions::kArcCustomTabsExperimentName,
      flag_descriptions::kArcCustomTabsExperimentDescription, kOsCrOS,
@@ -6694,6 +6734,11 @@
                                     kFoldableJankFixDelayVariations,
                                     "FoldableJankFix")},
 
+    {"enable-tab-strip-startup-refactoring",
+     flag_descriptions::kTabStripStartupRefactoringName,
+     flag_descriptions::kTabStripStartupRefactoringDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kTabStripStartupRefactoring)},
+
     {"enable-baseline-gm3-surface-colors",
      flag_descriptions::kBaselineGM3SurfaceColorsName,
      flag_descriptions::kBaselineGM3SurfaceColorsDescription, kOsAndroid,
@@ -9629,8 +9674,10 @@
      flag_descriptions::kHighEfficiencySavingsReportingImprovementsName,
      flag_descriptions::kHighEfficiencySavingsReportingImprovementsDescription,
      kOsDesktop,
-     FEATURE_VALUE_TYPE(
-         performance_manager::features::kMemorySavingsReportingImprovements)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(
+         performance_manager::features::kMemorySavingsReportingImprovements,
+         kHighEfficiencyMemorySavingsReportingVariations,
+         "MemorySavingsReportingImprovements")},
 
 #endif
 
diff --git a/chrome/browser/accessibility/pdf_ocr_controller.cc b/chrome/browser/accessibility/pdf_ocr_controller.cc
index 22e5822..bd86691 100644
--- a/chrome/browser/accessibility/pdf_ocr_controller.cc
+++ b/chrome/browser/accessibility/pdf_ocr_controller.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/accessibility/pdf_ocr_controller.h"
 
+#include "base/check_is_test.h"
 #include "base/check_op.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pdf_util.h"
@@ -97,7 +98,10 @@
   // TODO(crbug.com/1393069): Need to wait for the Screen AI library to be
   // installed if not ready yet. Then, set the AXMode for PDF OCR only when the
   // Screen AI library is downloaded and ready.
-  DCHECK(web_contents);
+  if (!web_contents) {
+    CHECK_IS_TEST();
+    return;
+  }
   // `web_contents` should be a PDF Viewer Mimehandler.
   DCHECK_EQ(web_contents->GetContentsMimeType(), kHtmlMimeType);
 
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
index 9ca39893..6156673 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
@@ -6,7 +6,6 @@
 
 #include "base/android/jni_android.h"
 #include "base/feature_list.h"
-#include "base/logging.h"
 #include "cc/resources/scoped_ui_resource.h"
 #include "cc/slim/layer.h"
 #include "cc/slim/solid_color_layer.h"
@@ -21,7 +20,6 @@
 
 using base::android::JavaParamRef;
 using base::android::JavaRef;
-bool tab_strip_redesign_enabled;
 
 namespace android {
 
@@ -37,6 +35,7 @@
       right_fade_(cc::slim::UIResourceLayer::Create()),
       model_selector_button_(cc::slim::UIResourceLayer::Create()),
       model_selector_button_background_(cc::slim::UIResourceLayer::Create()),
+      is_tab_strip_redesign_enabled_(is_tab_strip_redesign_enabled),
       write_index_(0),
       content_tree_(nullptr) {
   new_tab_button_->SetIsDrawable(true);
@@ -45,7 +44,6 @@
   model_selector_button_background_->SetIsDrawable(true);
   left_fade_->SetIsDrawable(true);
   right_fade_->SetIsDrawable(true);
-  tab_strip_redesign_enabled = is_tab_strip_redesign_enabled;
 
   // When the ScrollingStripStacker is used, the new tab button and tabs scroll,
   // while the incognito button and left/ride fade stay fixed. Put the new tab
@@ -59,15 +57,14 @@
   tab_strip_layer_->AddChild(model_selector_button_);
   tab_strip_layer_->AddChild(model_selector_button_background_);
   model_selector_button_background_->AddChild(model_selector_button_);
-  if (tab_strip_redesign_enabled) {
+  if (is_tab_strip_redesign_enabled_) {
     tab_strip_layer_->AddChild(new_tab_button_background_);
   }
   tab_strip_layer_->AddChild(new_tab_button_);
   layer()->AddChild(tab_strip_layer_);
 }
 
-TabStripSceneLayer::~TabStripSceneLayer() {
-}
+TabStripSceneLayer::~TabStripSceneLayer() = default;
 
 void TabStripSceneLayer::SetContentTree(
     JNIEnv* env,
@@ -153,7 +150,7 @@
   new_tab_button_->SetOpacity(button_alpha);
 
   // Set Tab Strip Redesign new tab button background
-  if (tab_strip_redesign_enabled) {
+  if (is_tab_strip_redesign_enabled_) {
     ui::Resource* button_background_resource =
         resource_manager->GetStaticResourceWithTint(bg_resource_id,
                                                     background_tint, true);
@@ -397,7 +394,7 @@
       y, width, height, content_offset_x, content_offset_y, divider_offset_x,
       bottom_offset_y, close_button_padding, close_button_alpha,
       is_start_divider_visible, is_end_divider_visible, is_loading,
-      spinner_rotation, brightness, opacity, tab_strip_redesign_enabled);
+      spinner_rotation, brightness, opacity, is_tab_strip_redesign_enabled_);
 }
 
 scoped_refptr<TabHandleLayer> TabStripSceneLayer::GetNextLayer(
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
index ea7275a8..5157225f 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
@@ -168,6 +168,8 @@
   scoped_refptr<cc::slim::UIResourceLayer> model_selector_button_;
   scoped_refptr<cc::slim::UIResourceLayer> model_selector_button_background_;
 
+  const bool is_tab_strip_redesign_enabled_ = false;
+
   unsigned write_index_;
   TabHandleLayerList tab_handle_layers_;
   raw_ptr<SceneLayer> content_tree_;
diff --git a/chrome/browser/android/customtabs/text_fragment_lookup_state_tracker.h b/chrome/browser/android/customtabs/text_fragment_lookup_state_tracker.h
index e255091..8e59854 100644
--- a/chrome/browser/android/customtabs/text_fragment_lookup_state_tracker.h
+++ b/chrome/browser/android/customtabs/text_fragment_lookup_state_tracker.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ANDROID_CUSTOMTABS_TEXT_FRAGMENT_LOOKUP_STATE_TRACKER_H_
 #define CHROME_BROWSER_ANDROID_CUSTOMTABS_TEXT_FRAGMENT_LOOKUP_STATE_TRACKER_H_
 
+#include "base/gtest_prod_util.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
diff --git a/chrome/browser/android/foreign_session_helper.cc b/chrome/browser/android/foreign_session_helper.cc
index 10e38b1..f71aed4 100644
--- a/chrome/browser/android/foreign_session_helper.cc
+++ b/chrome/browser/android/foreign_session_helper.cc
@@ -245,7 +245,8 @@
     last_pushed_session.Reset(Java_ForeignSessionHelper_pushSession(
         env, result, ConvertUTF8ToJavaString(env, session.GetSessionTag()),
         ConvertUTF8ToJavaString(env, session.GetSessionName()),
-        session.GetModifiedTime().ToJavaTime()));
+        session.GetModifiedTime().ToJavaTime(),
+        static_cast<int>(session.GetDeviceFormFactor())));
 
     // Push the full session, with tabs ordered by visual position.
     JNI_ForeignSessionHelper_CopySessionToJava(env, session,
@@ -280,7 +281,8 @@
       last_pushed_session.Reset(Java_ForeignSessionHelper_pushSession(
           env, result, ConvertUTF8ToJavaString(env, session->GetSessionTag()),
           ConvertUTF8ToJavaString(env, session->GetSessionName()),
-          session->GetModifiedTime().ToJavaTime()));
+          session->GetModifiedTime().ToJavaTime(),
+          static_cast<int>(session->GetDeviceFormFactor())));
 
       // Push the full session, with tabs ordered by visual position.
       JNI_ForeignSessionHelper_CopySessionToJava(env, *session,
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
index 5cc5c3c..1ded7129 100644
--- a/chrome/browser/android/shortcut_helper.cc
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -33,8 +33,7 @@
 void AddWebappWithSkBitmap(content::WebContents* web_contents,
                            const webapps::ShortcutInfo& info,
                            const std::string& webapp_id,
-                           const SkBitmap& icon_bitmap,
-                           bool is_icon_maskable) {
+                           const SkBitmap& icon_bitmap) {
   // Send the data to the Java side to create the shortcut.
   JNIEnv* env = base::android::AttachCurrentThread();
   ScopedJavaLocalRef<jstring> java_webapp_id =
@@ -59,7 +58,7 @@
   Java_ShortcutHelper_addWebapp(
       env, java_webapp_id, java_url, java_scope_url, java_user_title, java_name,
       java_short_name, java_best_primary_icon_url, java_bitmap,
-      is_icon_maskable, static_cast<int>(info.display),
+      info.is_primary_icon_maskable, static_cast<int>(info.display),
       static_cast<int>(info.orientation), info.source,
       ui::OptionalSkColorToJavaColor(info.theme_color),
       ui::OptionalSkColorToJavaColor(info.background_color));
@@ -75,8 +74,7 @@
 // Adds a shortcut which opens in a browser tab to the launcher.
 void AddShortcutWithSkBitmap(const webapps::ShortcutInfo& info,
                              const std::string& id,
-                             const SkBitmap& icon_bitmap,
-                             bool is_icon_maskable) {
+                             const SkBitmap& icon_bitmap) {
   JNIEnv* env = base::android::AttachCurrentThread();
   ScopedJavaLocalRef<jstring> java_id =
       base::android::ConvertUTF8ToJavaString(env, id);
@@ -91,8 +89,8 @@
   if (!icon_bitmap.drawsNothing())
     java_bitmap = gfx::ConvertToJavaBitmap(icon_bitmap);
   Java_ShortcutHelper_addShortcut(env, java_id, java_url, java_user_title,
-                                  java_bitmap, is_icon_maskable, info.source,
-                                  java_best_primary_icon_url);
+                                  java_bitmap, info.is_primary_icon_maskable,
+                                  info.source, java_best_primary_icon_url);
 }
 
 void RecordAddToHomeScreenUKM(
@@ -119,7 +117,6 @@
     content::WebContents* web_contents,
     const webapps::ShortcutInfo& info,
     const SkBitmap& icon_bitmap,
-    bool is_icon_maskable,
     webapps::InstallableStatusCode installable_status) {
   RecordAddToHomeScreenUKM(web_contents, info, installable_status);
 
@@ -127,11 +124,10 @@
   if (info.display == blink::mojom::DisplayMode::kStandalone ||
       info.display == blink::mojom::DisplayMode::kFullscreen ||
       info.display == blink::mojom::DisplayMode::kMinimalUi) {
-    AddWebappWithSkBitmap(web_contents, info, webapp_id, icon_bitmap,
-                          is_icon_maskable);
+    AddWebappWithSkBitmap(web_contents, info, webapp_id, icon_bitmap);
     return;
   }
-  AddShortcutWithSkBitmap(info, webapp_id, icon_bitmap, is_icon_maskable);
+  AddShortcutWithSkBitmap(info, webapp_id, icon_bitmap);
 }
 
 // static
diff --git a/chrome/browser/android/shortcut_helper.h b/chrome/browser/android/shortcut_helper.h
index d55d0f4..0f678b8 100644
--- a/chrome/browser/android/shortcut_helper.h
+++ b/chrome/browser/android/shortcut_helper.h
@@ -40,7 +40,6 @@
       content::WebContents* web_contents,
       const webapps::ShortcutInfo& info,
       const SkBitmap& icon_bitmap,
-      bool is_icon_maskable,
       webapps::InstallableStatusCode installable_status);
 
   // Stores the webapp splash screen in the WebappDataStorage associated with
diff --git a/chrome/browser/android/webapk/webapk_install_service.cc b/chrome/browser/android/webapk/webapk_install_service.cc
index c647a37..7626d75 100644
--- a/chrome/browser/android/webapk/webapk_install_service.cc
+++ b/chrome/browser/android/webapk/webapk_install_service.cc
@@ -46,7 +46,6 @@
     content::WebContents* web_contents,
     const webapps::ShortcutInfo& shortcut_info,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     webapps::WebappInstallSource install_source) {
   if (IsInstallInProgress(shortcut_info.manifest_id)) {
     webapps::WebappsUtils::ShowWebApkInstallResultToast(
@@ -57,19 +56,18 @@
   install_ids_.insert(shortcut_info.manifest_id);
   webapps::InstallableMetrics::TrackInstallEvent(install_source);
 
-  ShowInstallInProgressNotification(shortcut_info.manifest_id,
-                                    shortcut_info.short_name, shortcut_info.url,
-                                    primary_icon, is_primary_icon_maskable);
+  ShowInstallInProgressNotification(
+      shortcut_info.manifest_id, shortcut_info.short_name, shortcut_info.url,
+      primary_icon, shortcut_info.is_primary_icon_maskable);
 
   // We pass an weak ptr to a WebContents to the callback, since the
   // installation may take more than 10 seconds so there is a chance that the
   // WebContents has been destroyed before the install is finished.
   WebApkInstaller::InstallAsync(
       browser_context_, web_contents, shortcut_info, primary_icon,
-      is_primary_icon_maskable,
       base::BindOnce(&WebApkInstallService::OnFinishedInstall,
                      weak_ptr_factory_.GetWeakPtr(), web_contents->GetWeakPtr(),
-                     shortcut_info, primary_icon, is_primary_icon_maskable));
+                     shortcut_info, primary_icon));
 }
 
 void WebApkInstallService::InstallForServiceAsync(
@@ -102,8 +100,7 @@
 
   WebApkInstaller::InstallWithProtoAsync(
       browser_context_, std::move(serialized_proto), short_name,
-      webapps::ShortcutInfo::SOURCE_CHROME_SERVICE, primary_icon,
-      is_primary_icon_maskable, manifest_url,
+      webapps::ShortcutInfo::SOURCE_CHROME_SERVICE, primary_icon, manifest_url,
       base::BindOnce(&WebApkInstallService::OnFinishedInstallWithProto,
                      weak_ptr_factory_.GetWeakPtr(), manifest_id,
                      manifest_start_url, short_name, primary_icon,
@@ -139,8 +136,7 @@
 
   WebApkInstaller::InstallWithProtoAsync(
       browser_context_, std::move(serialized_proto), short_name,
-      webapps::ShortcutInfo::SOURCE_INSTALL_RETRY, primary_icon,
-      is_primary_icon_maskable, manifest_url,
+      webapps::ShortcutInfo::SOURCE_INSTALL_RETRY, primary_icon, manifest_url,
       base::BindOnce(&WebApkInstallService::OnFinishedInstallWithProto,
                      weak_ptr_factory_.GetWeakPtr(), manifest_id,
                      manifest_start_url, short_name, primary_icon,
@@ -160,7 +156,6 @@
     base::WeakPtr<content::WebContents> web_contents,
     const webapps::ShortcutInfo& shortcut_info,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     webapps::WebApkInstallResult result,
     std::unique_ptr<std::string> serialized_proto,
     bool relax_updates,
@@ -168,8 +163,9 @@
   install_ids_.erase(shortcut_info.manifest_id);
   HandleFinishInstallNotifications(
       shortcut_info.manifest_id, shortcut_info.url, shortcut_info.short_name,
-      primary_icon, is_primary_icon_maskable, shortcut_info.source, result,
-      std::move(serialized_proto), webapk_package_name);
+      primary_icon, shortcut_info.is_primary_icon_maskable,
+      shortcut_info.source, result, std::move(serialized_proto),
+      webapk_package_name);
 
   if (base::FeatureList::IsEnabled(
           webapps::features::kWebApkInstallFailureNotification)) {
@@ -188,7 +184,6 @@
     // TODO(https://crbug.com/861643): Support maskable icons here.
     ShortcutHelper::AddToLauncherWithSkBitmap(
         web_contents.get(), shortcut_info, primary_icon,
-        /*is_icon_maskable=*/false,
         webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED);
   }
 }
diff --git a/chrome/browser/android/webapk/webapk_install_service.h b/chrome/browser/android/webapk/webapk_install_service.h
index e6e85784..0184764 100644
--- a/chrome/browser/android/webapk/webapk_install_service.h
+++ b/chrome/browser/android/webapk/webapk_install_service.h
@@ -76,7 +76,6 @@
   void InstallAsync(content::WebContents* web_contents,
                     const webapps::ShortcutInfo& shortcut_info,
                     const SkBitmap& primary_icon,
-                    bool is_primary_icon_maskable,
                     webapps::WebappInstallSource install_source);
 
   void RetryInstallAsync(std::unique_ptr<std::string> serialized_web_apk,
@@ -108,7 +107,6 @@
   void OnFinishedInstall(base::WeakPtr<content::WebContents> web_contents,
                          const webapps::ShortcutInfo& shortcut_info,
                          const SkBitmap& primary_icon,
-                         bool is_priamry_icon_maskable,
                          webapps::WebApkInstallResult result,
                          std::unique_ptr<std::string> serialized_webapk,
                          bool relax_updates,
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index 69a0a78..a69959e 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -135,12 +135,11 @@
                                    content::WebContents* web_contents,
                                    const webapps::ShortcutInfo& shortcut_info,
                                    const SkBitmap& primary_icon,
-                                   bool is_primary_icon_maskable,
                                    FinishCallback finish_callback) {
   // The installer will delete itself when it is done.
   WebApkInstaller* installer = new WebApkInstaller(context);
   installer->InstallAsync(web_contents, shortcut_info, primary_icon,
-                          is_primary_icon_maskable, std::move(finish_callback));
+                          std::move(finish_callback));
 }
 
 // static
@@ -150,14 +149,13 @@
     const std::u16string& short_name,
     webapps::ShortcutInfo::Source source,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     GURL& manifest_url,
     FinishCallback finish_callback) {
   // The installer will delete itself when it is done.
   WebApkInstaller* installer = new WebApkInstaller(context);
-  installer->InstallWithProtoAsync(
-      std::move(serialized_webapk), short_name, source, primary_icon,
-      is_primary_icon_maskable, manifest_url, std::move(finish_callback));
+  installer->InstallWithProtoAsync(std::move(serialized_webapk), short_name,
+                                   source, primary_icon, manifest_url,
+                                   std::move(finish_callback));
 }
 
 // static
@@ -175,10 +173,9 @@
     content::WebContents* web_contents,
     const webapps::ShortcutInfo& shortcut_info,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     FinishCallback callback) {
   installer->InstallAsync(web_contents, shortcut_info, primary_icon,
-                          is_primary_icon_maskable, std::move(callback));
+                          std::move(callback));
 }
 
 // static
@@ -188,12 +185,11 @@
     const std::u16string& short_name,
     webapps::ShortcutInfo::Source source,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     GURL& manifest_url,
     FinishCallback callback) {
-  installer->InstallWithProtoAsync(
-      std::move(serialized_webapk), short_name, source, primary_icon,
-      is_primary_icon_maskable, manifest_url, std::move(callback));
+  installer->InstallWithProtoAsync(std::move(serialized_webapk), short_name,
+                                   source, primary_icon, manifest_url,
+                                   std::move(callback));
 }
 
 // static
@@ -218,7 +214,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
@@ -230,12 +225,12 @@
     base::OnceCallback<void(bool)> callback) {
   GetBackgroundTaskRunner()->PostTaskAndReplyWithResult(
       FROM_HERE,
-      base::BindOnce(
-          &webapps::StoreUpdateRequestToFileInBackground, update_request_path,
-          shortcut_info, app_key, primary_icon_data, is_primary_icon_maskable,
-          splash_icon_data, package_name, version,
-          std::move(icon_url_to_murmur2_hash), is_manifest_stale,
-          is_app_identity_update_supported, std::move(update_reasons)),
+      base::BindOnce(&webapps::StoreUpdateRequestToFileInBackground,
+                     update_request_path, shortcut_info, app_key,
+                     primary_icon_data, splash_icon_data, package_name, version,
+                     std::move(icon_url_to_murmur2_hash), is_manifest_stale,
+                     is_app_identity_update_supported,
+                     std::move(update_reasons)),
       std::move(callback));
 }
 
@@ -313,7 +308,6 @@
 void WebApkInstaller::InstallAsync(content::WebContents* web_contents,
                                    const webapps::ShortcutInfo& shortcut_info,
                                    const SkBitmap& primary_icon,
-                                   bool is_primary_icon_maskable,
                                    FinishCallback finish_callback) {
   DCHECK(!install_from_webapk_service_);
   install_duration_timer_ = std::make_unique<base::ElapsedTimer>();
@@ -322,7 +316,6 @@
   install_shortcut_info_ =
       std::make_unique<webapps::ShortcutInfo>(shortcut_info);
   install_primary_icon_ = primary_icon;
-  is_primary_icon_maskable_ = is_primary_icon_maskable;
   short_name_ = shortcut_info.short_name;
   finish_callback_ = std::move(finish_callback);
   source_ = install_shortcut_info_->source;
@@ -342,7 +335,6 @@
     const std::u16string& short_name,
     webapps::ShortcutInfo::Source source,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     GURL& manifest_url,
     FinishCallback finish_callback) {
   install_duration_timer_ = std::make_unique<base::ElapsedTimer>();
@@ -351,7 +343,6 @@
   short_name_ = short_name;
   manifest_url_ = manifest_url;
   install_primary_icon_ = primary_icon;
-  is_primary_icon_maskable_ = is_primary_icon_maskable;
   source_ = source;
   serialized_webapk_ = std::move(serialized_webapk);
   finish_callback_ = std::move(finish_callback);
@@ -604,7 +595,7 @@
   // because in WebApk installs, we are using the icon data from |hashes|.
   webapps::BuildProto(
       *install_shortcut_info_, install_shortcut_info_->manifest_id,
-      std::string() /* primary_icon_data */, is_primary_icon_maskable_,
+      std::string() /* primary_icon_data */,
       std::string() /* splash_icon_data */, "" /* package_name */,
       "" /* version */, std::move(*hashes), false /* is_manifest_stale */,
       false /* is_app_identity_update_supported */,
diff --git a/chrome/browser/android/webapk/webapk_installer.h b/chrome/browser/android/webapk/webapk_installer.h
index 919af6d2..ba03e3a 100644
--- a/chrome/browser/android/webapk/webapk_installer.h
+++ b/chrome/browser/android/webapk/webapk_installer.h
@@ -74,7 +74,6 @@
                            content::WebContents* web_contents,
                            const webapps::ShortcutInfo& shortcut_info,
                            const SkBitmap& primary_icon,
-                           bool is_primary_icon_maskable,
                            FinishCallback finish_callback);
 
   // Creates a self-owned WebApkInstaller instance and talks to the Chrome
@@ -89,7 +88,6 @@
       const std::u16string& short_name,
       webapps::ShortcutInfo::Source source,
       const SkBitmap& primary_icon,
-      bool is_primary_icon_maskable,
       GURL& manifest_url,
       FinishCallback finish_callback);
 
@@ -107,7 +105,6 @@
                                      content::WebContents* web_contents,
                                      const webapps::ShortcutInfo& shortcut_info,
                                      const SkBitmap& primary_icon,
-                                     bool is_primary_icon_maskable,
                                      FinishCallback callback);
 
   // Calls the private function |InstallWithProtoAsync| for testing.
@@ -118,7 +115,6 @@
       const std::u16string& short_name,
       webapps::ShortcutInfo::Source source,
       const SkBitmap& primary_icon,
-      bool is_primary_icon_maskable,
       GURL& manifest_url,
       FinishCallback callback);
 
@@ -148,7 +144,6 @@
       const webapps::ShortcutInfo& shortcut_info,
       const GURL& app_key,
       const std::string& primary_icon_data,
-      bool is_primary_icon_maskable,
       const std::string& splash_icon_data,
       const std::string& package_name,
       const std::string& version,
@@ -189,7 +184,6 @@
   void InstallAsync(content::WebContents* web_contents,
                     const webapps::ShortcutInfo& shortcut_info,
                     const SkBitmap& primary_icon,
-                    bool is_primary_icon_maskable,
                     FinishCallback finish_callback);
 
   // Talks to the Chrome WebAPK server to update a WebAPK on the server and to
@@ -206,7 +200,6 @@
                              const std::u16string& short_name,
                              webapps::ShortcutInfo::Source source,
                              const SkBitmap& primary_icon,
-                             bool is_primary_icon_maskable,
                              GURL& manifest_url,
                              FinishCallback finish_callback);
 
@@ -269,7 +262,6 @@
   std::unique_ptr<webapps::ShortcutInfo> install_shortcut_info_;
 
   SkBitmap install_primary_icon_;
-  bool is_primary_icon_maskable_;
 
   std::u16string short_name_;
 
diff --git a/chrome/browser/android/webapk/webapk_installer_unittest.cc b/chrome/browser/android/webapk/webapk_installer_unittest.cc
index b693f3f..5117dee 100644
--- a/chrome/browser/android/webapk/webapk_installer_unittest.cc
+++ b/chrome/browser/android/webapk/webapk_installer_unittest.cc
@@ -122,7 +122,7 @@
 
     // WebApkInstaller owns itself.
     WebApkInstaller::InstallAsyncForTesting(
-        installer.release(), web_contents, info, SkBitmap(), false,
+        installer.release(), web_contents, info, SkBitmap(),
         base::BindOnce(&WebApkInstallerRunner::OnCompleted,
                        base::Unretained(this)));
 
@@ -141,7 +141,7 @@
     // WebApkInstaller owns itself.
     WebApkInstaller::InstallWithProtoAsyncForTesting(
         installer.release(), std::move(serialized_webapk), short_name, source,
-        SkBitmap(), false, manifest_url,
+        SkBitmap(), manifest_url,
         base::BindOnce(&WebApkInstallerRunner::OnCompleted,
                        base::Unretained(this)));
 
@@ -193,9 +193,9 @@
     base::RunLoop run_loop;
     quit_closure_ = run_loop.QuitClosure();
     WebApkInstaller::StoreUpdateRequestToFile(
-        update_request_path, webapps::ShortcutInfo((GURL())), GURL(), "", false,
-        "", "", "", std::map<std::string, webapps::WebApkIconHasher::Icon>(),
-        false, false, {webapps::WebApkUpdateReason::PRIMARY_ICON_HASH_DIFFERS},
+        update_request_path, webapps::ShortcutInfo((GURL())), GURL(), "", "",
+        "", "", std::map<std::string, webapps::WebApkIconHasher::Icon>(), false,
+        false, {webapps::WebApkUpdateReason::PRIMARY_ICON_HASH_DIFFERS},
         base::BindOnce(&UpdateRequestStorer::OnComplete,
                        base::Unretained(this)));
     run_loop.Run();
@@ -299,7 +299,7 @@
     webapps::ShortcutInfo info(GURL::EmptyGURL());
 
     return webapps::BuildProtoInBackground(
-        info, info.manifest_id, primary_icon_data, false, splash_icon_data,
+        info, info.manifest_id, primary_icon_data, splash_icon_data,
         /*package_name*/ "", /*version*/ "",
         std::move(icon_url_to_murmur2_hash), true /* is_manifest_stale */,
         true /* is_app_identity_update_supported */,
diff --git a/chrome/browser/android/webapk/webapk_update_manager.cc b/chrome/browser/android/webapk/webapk_update_manager.cc
index 85a4711..430b532 100644
--- a/chrome/browser/android/webapk/webapk_update_manager.cc
+++ b/chrome/browser/android/webapk/webapk_update_manager.cc
@@ -113,6 +113,7 @@
   info.background_color = ui::JavaColorToOptionalSkColor(java_background_color);
   info.best_primary_icon_url =
       GURL(ConvertJavaStringToUTF8(env, java_primary_icon_url));
+  info.is_primary_icon_maskable = java_is_primary_icon_maskable;
   info.splash_image_url =
       GURL(ConvertJavaStringToUTF8(env, java_splash_icon_url));
   info.is_splash_image_maskable = java_is_splash_icon_maskable;
@@ -223,7 +224,7 @@
 
   WebApkInstaller::StoreUpdateRequestToFile(
       base::FilePath(update_request_path), info, app_key, primary_icon_data,
-      java_is_primary_icon_maskable, splash_icon_data, webapk_package,
+      splash_icon_data, webapk_package,
       base::NumberToString(java_webapk_version),
       std::move(icon_url_to_murmur2_hash), java_is_manifest_stale,
       java_is_app_identity_update_supported, std::move(update_reasons),
diff --git a/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc b/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc
index 8698092..d76b6e49 100644
--- a/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc
+++ b/chrome/browser/apps/app_deduplication_service/app_deduplication_service.cc
@@ -55,6 +55,10 @@
 
 AppDeduplicationService::~AppDeduplicationService() = default;
 
+bool AppDeduplicationService::IsServiceOn() {
+  return !duplication_map_.empty();
+}
+
 void AppDeduplicationService::StartLoginFlow() {
   const int hours_diff =
       std::abs((GetServerPref() - base::Time::Now()).InHours());
diff --git a/chrome/browser/apps/app_deduplication_service/app_deduplication_service.h b/chrome/browser/apps/app_deduplication_service/app_deduplication_service.h
index 2e9f249..ef78048 100644
--- a/chrome/browser/apps/app_deduplication_service/app_deduplication_service.h
+++ b/chrome/browser/apps/app_deduplication_service/app_deduplication_service.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
@@ -39,6 +40,10 @@
   AppDeduplicationService(const AppDeduplicationService&) = delete;
   AppDeduplicationService& operator=(const AppDeduplicationService&) = delete;
 
+  // Call this function before using any other function.
+  // This function returns true if the Deduplication Service has been
+  // properly initialised, ensuring the correctness of method responses.
+  bool IsServiceOn();
   std::vector<Entry> GetDuplicates(const EntryId& entry_id);
   bool AreDuplicates(const EntryId& entry_id_1, const EntryId& entry_id_2);
 
@@ -61,6 +66,10 @@
                            PrefUnchangedAfterServerError);
   FRIEND_TEST_ALL_PREFIXES(AppDeduplicationServiceAlmanacTest,
                            PrefSetAfterServerSuccess);
+  FRIEND_TEST_ALL_PREFIXES(AppDeduplicationServiceAlmanacTest,
+                           ValidServiceNoDuplicates);
+  FRIEND_TEST_ALL_PREFIXES(AppDeduplicationServiceAlmanacTest,
+                           ValidServiceWithDuplicates);
 
   enum class EntryStatus {
     // This entry is not an app entry (could be website, phonehub, etc.).
diff --git a/chrome/browser/apps/app_deduplication_service/app_deduplication_service_unittest.cc b/chrome/browser/apps/app_deduplication_service/app_deduplication_service_unittest.cc
index 765519d7..51d84e0 100644
--- a/chrome/browser/apps/app_deduplication_service/app_deduplication_service_unittest.cc
+++ b/chrome/browser/apps/app_deduplication_service/app_deduplication_service_unittest.cc
@@ -506,8 +506,12 @@
   auto* service = AppDeduplicationServiceFactory::GetForProfile(&profile);
   EXPECT_NE(nullptr, service);
 
+  // This function is called to populate the duplicate map, or else IsServiceOn
+  // will return false.
   service->DeduplicateDataToEntries(data);
 
+  EXPECT_TRUE(service->IsServiceOn());
+
   uint32_t skype_test_index = 1;
 
   std::string skype_android_app_id = "com.skype.raider";
@@ -598,4 +602,42 @@
   EXPECT_TRUE(time_before < time_after);
 }
 
+TEST_F(AppDeduplicationServiceAlmanacTest, ValidServiceNoDuplicates) {
+  proto::DeduplicateData data;
+
+  auto* viber_group = data.add_app_group();
+  viber_group->set_app_group_uuid("af45163b-111d-4d43-b191-01a9f8aece4c");
+  viber_group->add_package_id();
+  viber_group->set_package_id(0, "android:com.viber.voip");
+
+  std::string viber_arc_app_id = "com.viber.voip";
+
+  TestingProfile profile;
+  ASSERT_TRUE(AppDeduplicationServiceFactory::
+                  IsAppDeduplicationServiceAvailableForProfile(&profile));
+  auto* service = AppDeduplicationServiceFactory::GetForProfile(&profile);
+
+  EXPECT_NE(nullptr, service);
+
+  // This function is called to populate the duplicate map, or else IsServiceOn
+  // will return false.
+  service->DeduplicateDataToEntries(data);
+
+  EXPECT_TRUE(service->IsServiceOn());
+
+  EntryId viber_arc_entry_id(viber_arc_app_id, apps::AppType::kArc);
+
+  EXPECT_TRUE(service->GetDuplicates(viber_arc_entry_id).empty());
+}
+
+TEST_F(AppDeduplicationServiceAlmanacTest, InvalidServiceNoDuplicates) {
+  TestingProfile profile;
+  ASSERT_TRUE(AppDeduplicationServiceFactory::
+                  IsAppDeduplicationServiceAvailableForProfile(&profile));
+  auto* service = AppDeduplicationServiceFactory::GetForProfile(&profile);
+
+  EXPECT_NE(nullptr, service);
+
+  EXPECT_FALSE(service->IsServiceOn());
+}
 }  // namespace apps::deduplication
diff --git a/chrome/browser/apps/app_service/BUILD.gn b/chrome/browser/apps/app_service/BUILD.gn
index 9cc21f4a..9591a93 100644
--- a/chrome/browser/apps/app_service/BUILD.gn
+++ b/chrome/browser/apps/app_service/BUILD.gn
@@ -238,7 +238,7 @@
       "//chrome/browser/ash/system_web_apps",
       "//chrome/browser/ash/system_web_apps/types",
       "//chrome/browser/image_decoder",
-      "//chrome/browser/metrics/structured",
+      "//chrome/browser/metrics/structured:features",
       "//chrome/browser/resources",
       "//chromeos/ash/components/login/login_state",
       "//components/app_restore",
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.h b/chrome/browser/apps/app_service/app_service_proxy_ash.h
index 34d1a32..b4adb13 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_ash.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_ash.h
@@ -12,6 +12,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.h b/chrome/browser/apps/app_service/app_service_proxy_lacros.h
index 427f991..88e34ca 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.h
@@ -11,6 +11,7 @@
 
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "build/chromeos_buildflags.h"
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
index 48d99d96..7c0f2fd 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
index 6b95fc2c..2277fa8 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index fc27b70..8c988b9 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -1018,7 +1018,8 @@
       "document.body.appendChild(ae);"
       "ae.play();",
       audio_url.spec().c_str());
-  EXPECT_TRUE(content::ExecJs(guest, setup_audio_script));
+  EXPECT_TRUE(content::ExecJs(guest, setup_audio_script,
+                              content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
 
   // Wait for audio to start.
   embedder_obs.WaitForCurrentlyAudible(true);
@@ -1687,8 +1688,9 @@
   EXPECT_EQ(true,
             content::EvalJs(new_window_guest_frame, "!!window.newWindow"));
   if (IsNewWindowRestricted()) {
-    EXPECT_EQ(false, content::EvalJs(new_window_guest_frame,
-                                     "!!window.newWindow.location.href"));
+    EXPECT_EQ(url::kAboutBlankURL,
+              content::EvalJs(new_window_guest_frame,
+                              "window.newWindow.location.href"));
   } else {
     EXPECT_EQ(empty_guest_frame->GetLastCommittedURL(),
               content::EvalJs(new_window_guest_frame,
@@ -5606,8 +5608,8 @@
   GetGuestViewManager()->WaitForSingleGuestRenderFrameHostCreated();
 
   content::WebContents* embedder = GetEmbedderWebContents();
-  EXPECT_TRUE(ExecuteScript(embedder,
-                            "document.querySelector('webview').name = 'foo';"));
+  EXPECT_TRUE(
+      ExecJs(embedder, "document.querySelector('webview').name = 'foo';"));
   extensions::WebViewGuest* guest =
       extensions::WebViewGuest::FromGuestViewBase(GetGuestView());
   EXPECT_EQ("foo", guest->name());
@@ -5636,8 +5638,7 @@
 
   // Reload guest and make sure its window.name is preserved.
   content::TestFrameNavigationObserver load_observer(GetGuestRenderFrameHost());
-  EXPECT_TRUE(
-      ExecuteScript(embedder, "document.querySelector('webview').reload()"));
+  EXPECT_TRUE(ExecJs(embedder, "document.querySelector('webview').reload()"));
   load_observer.Wait();
   EXPECT_EQ("foo", content::EvalJs(GetGuestRenderFrameHost(), "window.name"));
 }
diff --git a/chrome/browser/apps/platform_apps/api/enterprise_remote_apps/enterprise_remote_apps_apitest.cc b/chrome/browser/apps/platform_apps/api/enterprise_remote_apps/enterprise_remote_apps_apitest.cc
index e81b20da..238b277 100644
--- a/chrome/browser/apps/platform_apps/api/enterprise_remote_apps/enterprise_remote_apps_apitest.cc
+++ b/chrome/browser/apps/platform_apps/api/enterprise_remote_apps/enterprise_remote_apps_apitest.cc
@@ -22,10 +22,10 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/remote_apps/id_generator.h"
 #include "chrome/browser/ash/remote_apps/remote_apps_manager.h"
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 192f4a7..76f6664 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1152,6 +1152,8 @@
     "file_manager/trash_info_validator.h",
     "file_manager/trash_io_task.cc",
     "file_manager/trash_io_task.h",
+    "file_manager/uma_enums.gen.cc",
+    "file_manager/uma_enums.gen.h",
     "file_manager/url_util.cc",
     "file_manager/url_util.h",
     "file_manager/volume.cc",
@@ -1588,12 +1590,8 @@
     "login/easy_unlock/easy_unlock_service.h",
     "login/easy_unlock/easy_unlock_service_factory.cc",
     "login/easy_unlock/easy_unlock_service_factory.h",
-    "login/easy_unlock/easy_unlock_service_regular.cc",
-    "login/easy_unlock/easy_unlock_service_regular.h",
     "login/easy_unlock/smartlock_feature_usage_metrics.cc",
     "login/easy_unlock/smartlock_feature_usage_metrics.h",
-    "login/easy_unlock/smartlock_state_handler.cc",
-    "login/easy_unlock/smartlock_state_handler.h",
     "login/enrollment/auto_enrollment_check_screen.cc",
     "login/enrollment/auto_enrollment_check_screen.h",
     "login/enrollment/auto_enrollment_check_screen_view.h",
@@ -1960,8 +1958,6 @@
     "login/ui/webui_login_view.h",
     "login/user_board_view_mojo.cc",
     "login/user_board_view_mojo.h",
-    "login/user_flow.cc",
-    "login/user_flow.h",
     "login/user_online_signin_notifier.cc",
     "login/user_online_signin_notifier.h",
     "login/users/affiliation.cc",
@@ -3806,7 +3802,7 @@
     "//chrome/browser/devtools",
     "//chrome/browser/favicon",
     "//chrome/browser/google",
-    "//chrome/browser/metrics/structured",
+    "//chrome/browser/metrics/structured:features",
     "//chrome/browser/nearby_sharing/common",
     "//chrome/browser/nearby_sharing/logging",
     "//chrome/browser/nearby_sharing/public/cpp",
@@ -4417,8 +4413,6 @@
     "login/test/device_state_mixin.h",
     "login/test/dialog_window_waiter.cc",
     "login/test/dialog_window_waiter.h",
-    "login/test/embedded_policy_test_server_mixin.cc",
-    "login/test/embedded_policy_test_server_mixin.h",
     "login/test/embedded_test_server_setup_mixin.cc",
     "login/test/embedded_test_server_setup_mixin.h",
     "login/test/js_checker.cc",
@@ -4441,7 +4435,6 @@
     "login/test/oobe_screen_waiter.h",
     "login/test/oobe_screens_utils.cc",
     "login/test/oobe_screens_utils.h",
-    "login/test/policy_test_server_constants.h",
     "login/test/profile_prepared_waiter.cc",
     "login/test/profile_prepared_waiter.h",
     "login/test/scoped_help_app_for_test.cc",
@@ -4519,6 +4512,9 @@
     "policy/reporting/metrics_reporting/cros_healthd_info_metric_sampler_test_utils.h",
     "policy/reporting/metrics_reporting/network/fake_network_diagnostics_util.cc",
     "policy/reporting/metrics_reporting/network/fake_network_diagnostics_util.h",
+    "policy/test_support/embedded_policy_test_server_mixin.cc",
+    "policy/test_support/embedded_policy_test_server_mixin.h",
+    "policy/test_support/policy_test_server_constants.h",
     "printing/history/mock_print_job_history_service.cc",
     "printing/history/mock_print_job_history_service.h",
     "printing/history/test_print_job_database.cc",
@@ -5150,6 +5146,7 @@
     "file_manager/trash_io_task_unittest.cc",
     "file_manager/trash_unittest_base.cc",
     "file_manager/trash_unittest_base.h",
+    "file_manager/uma_enums_unittest.cc",
     "file_manager/url_util_unittest.cc",
     "file_manager/volume_manager_unittest.cc",
     "file_suggest/file_suggest_keyed_service_unittest.cc",
@@ -5287,7 +5284,7 @@
     "login/demo_mode/demo_setup_controller_unittest.cc",
     "login/easy_unlock/easy_unlock_auth_attempt_unittest.cc",
     "login/easy_unlock/easy_unlock_notification_controller_unittest.cc",
-    "login/easy_unlock/easy_unlock_service_regular_unittest.cc",
+    "login/easy_unlock/easy_unlock_service_unittest.cc",
     "login/easy_unlock/smartlock_feature_usage_metrics_unittest.cc",
     "login/enrollment/enrollment_screen_unittest.cc",
     "login/enterprise_user_session_metrics_unittest.cc",
diff --git a/chrome/browser/ash/accessibility/html_test_utils.cc b/chrome/browser/ash/accessibility/html_test_utils.cc
index 0e24618f..5137192 100644
--- a/chrome/browser/ash/accessibility/html_test_utils.cc
+++ b/chrome/browser/ash/accessibility/html_test_utils.cc
@@ -13,7 +13,7 @@
 
 void ExecuteScript(content::WebContents* web_contents,
                    const std::string& script) {
-  ASSERT_TRUE(content::ExecuteScript(web_contents, script));
+  ASSERT_TRUE(content::ExecJs(web_contents, script));
 }
 
 gfx::Rect GetControlBoundsInRoot(content::WebContents* web_contents,
diff --git a/chrome/browser/ash/app_list/app_list_model_updater.h b/chrome/browser/ash/app_list/app_list_model_updater.h
index d746425..7d62d45 100644
--- a/chrome/browser/ash/app_list/app_list_model_updater.h
+++ b/chrome/browser/ash/app_list/app_list_model_updater.h
@@ -11,6 +11,7 @@
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ash/app_list/app_list_model_updater_observer.h"
 #include "chrome/browser/ash/app_list/app_list_syncable_service.h"
diff --git a/chrome/browser/ash/app_list/launcher_search_iph_browsertest.cc b/chrome/browser/ash/app_list/launcher_search_iph_browsertest.cc
index 4dadf9f9..b58dffc 100644
--- a/chrome/browser/ash/app_list/launcher_search_iph_browsertest.cc
+++ b/chrome/browser/ash/app_list/launcher_search_iph_browsertest.cc
@@ -474,11 +474,6 @@
 
 IN_PROC_BROWSER_TEST_P(AppListIphBrowserTestWithLearnMoreToast,
                        ShowAssistantLearnMoreToast) {
-  // TODO(b/276970723): Disables tablet mode variant for a crash.
-  if (IsTabletModeTest()) {
-    GTEST_SKIP() << "b/276970723";
-  }
-
   OpenAppList();
 
   views::ImageButton* assistant_button = search_box_view()->assistant_button();
diff --git a/chrome/browser/ash/app_restore/arc_app_single_restore_handler.h b/chrome/browser/ash/app_restore/arc_app_single_restore_handler.h
index 2992739..7d7d131 100644
--- a/chrome/browser/ash/app_restore/arc_app_single_restore_handler.h
+++ b/chrome/browser/ash/app_restore/arc_app_single_restore_handler.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
diff --git a/chrome/browser/ash/app_restore/arc_ghost_window_handler.h b/chrome/browser/ash/app_restore/arc_ghost_window_handler.h
index 67ad2e3..cbb6ad40 100644
--- a/chrome/browser/ash/app_restore/arc_ghost_window_handler.h
+++ b/chrome/browser/ash/app_restore/arc_ghost_window_handler.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ASH_APP_RESTORE_ARC_GHOST_WINDOW_HANDLER_H_
 
 #include "ash/components/arc/mojom/app.mojom.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
diff --git a/chrome/browser/ash/app_restore/full_restore_service.h b/chrome/browser/ash/app_restore/full_restore_service.h
index 68c964e..facc489a 100644
--- a/chrome/browser/ash/app_restore/full_restore_service.h
+++ b/chrome/browser/ash/app_restore/full_restore_service.h
@@ -9,6 +9,7 @@
 
 #include "ash/public/cpp/accelerators.h"
 #include "base/callback_list.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
diff --git a/chrome/browser/ash/arc/fileapi/arc_documents_provider_root.h b/chrome/browser/ash/arc/fileapi/arc_documents_provider_root.h
index 9fca7c6c..48a76c4 100644
--- a/chrome/browser/ash/arc/fileapi/arc_documents_provider_root.h
+++ b/chrome/browser/ash/arc/fileapi/arc_documents_provider_root.h
@@ -14,6 +14,7 @@
 #include "ash/components/arc/mojom/file_system.mojom-forward.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
diff --git a/chrome/browser/ash/arc/idle_manager/arc_idle_manager.cc b/chrome/browser/ash/arc/idle_manager/arc_idle_manager.cc
index 8beda8a..0fb47a11 100644
--- a/chrome/browser/ash/arc/idle_manager/arc_idle_manager.cc
+++ b/chrome/browser/ash/arc/idle_manager/arc_idle_manager.cc
@@ -106,6 +106,11 @@
 // cleanup in Shutdown();
 ArcIdleManager::~ArcIdleManager() = default;
 
+// static
+void ArcIdleManager::EnsureFactoryBuilt() {
+  ArcIdleManagerFactory::GetInstance();
+}
+
 void ArcIdleManager::Shutdown() {
   // After this is done, we will no longer get connection notifications.
   bridge_->power()->RemoveObserver(this);
diff --git a/chrome/browser/ash/arc/idle_manager/arc_idle_manager.h b/chrome/browser/ash/arc/idle_manager/arc_idle_manager.h
index 14546deb..9238524 100644
--- a/chrome/browser/ash/arc/idle_manager/arc_idle_manager.h
+++ b/chrome/browser/ash/arc/idle_manager/arc_idle_manager.h
@@ -53,6 +53,8 @@
   static ArcIdleManager* GetForBrowserContextForTesting(
       content::BrowserContext* context);
 
+  static void EnsureFactoryBuilt();
+
   // KeyedService:
   void Shutdown() override;
 
diff --git a/chrome/browser/ash/arc/idle_manager/arc_window_observer.cc b/chrome/browser/ash/arc/idle_manager/arc_window_observer.cc
index e44ff36..ef04a3c 100644
--- a/chrome/browser/ash/arc/idle_manager/arc_window_observer.cc
+++ b/chrome/browser/ash/arc/idle_manager/arc_window_observer.cc
@@ -14,10 +14,10 @@
     const ObserverStateChangedCallback& callback) {
   ThrottleObserver::StartObserving(context, callback);
 
-  OnArcWindowCountChanged(
-      ash::ArcWindowWatcher::instance()->GetArcWindowCount());
-
-  observation_.Observe(ash::ArcWindowWatcher::instance());
+  if (auto* instance = ash::ArcWindowWatcher::instance()) {
+    OnArcWindowCountChanged(instance->GetArcWindowCount());
+    observation_.Observe(instance);
+  }
 }
 
 ArcWindowObserver::~ArcWindowObserver() = default;
diff --git a/chrome/browser/ash/arc/net/cert_manager_impl.h b/chrome/browser/ash/arc/net/cert_manager_impl.h
index 876d9fee..ab0c2a4d 100644
--- a/chrome/browser/ash/arc/net/cert_manager_impl.h
+++ b/chrome/browser/ash/arc/net/cert_manager_impl.h
@@ -8,8 +8,8 @@
 #include <string>
 
 #include "ash/components/arc/net/cert_manager.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
-
 #include "chrome/browser/net/nss_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "net/cert/nss_cert_database.h"
diff --git a/chrome/browser/ash/arc/optin/arc_optin_preference_handler.cc b/chrome/browser/ash/arc/optin/arc_optin_preference_handler.cc
index 585b918..1e41fb0 100644
--- a/chrome/browser/ash/arc/optin/arc_optin_preference_handler.cc
+++ b/chrome/browser/ash/arc/optin/arc_optin_preference_handler.cc
@@ -9,6 +9,7 @@
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "chrome/browser/ash/arc/optin/arc_optin_preference_handler_observer.h"
+#include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/ash/settings/stats_reporting_controller.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/api/settings_private/prefs_util.h"
@@ -18,35 +19,18 @@
 #include "components/metrics/metrics_service.h"
 #include "components/prefs/pref_service.h"
 
-namespace {
-
-bool ShouldUpdateUserConsent() {
-  // Return user consent should not be used if feature is disabled.
-  if (!base::FeatureList::IsEnabled(ash::features::kPerUserMetrics))
-    return false;
-
-  auto* metrics_service = g_browser_process->metrics_service();
-
-  if (!metrics_service ||
-      !metrics_service->GetCurrentUserMetricsConsent().has_value()) {
-    return false;
-  }
-
-  // Per user metrics should be disabled if the device metrics was disabled by
-  // the owner.
-  return ash::StatsReportingController::Get()->IsEnabled();
-}
-
-}  // namespace
-
 namespace arc {
 
 ArcOptInPreferenceHandler::ArcOptInPreferenceHandler(
     ArcOptInPreferenceHandlerObserver* observer,
-    PrefService* pref_service)
-    : observer_(observer), pref_service_(pref_service) {
+    PrefService* pref_service,
+    metrics::MetricsService* metrics_service)
+    : observer_(observer),
+      pref_service_(pref_service),
+      metrics_service_(metrics_service) {
   DCHECK(observer_);
   DCHECK(pref_service_);
+  DCHECK(metrics_service_);
 }
 
 void ArcOptInPreferenceHandler::Start() {
@@ -66,7 +50,6 @@
       base::BindRepeating(
           &ArcOptInPreferenceHandler::OnLocationServicePreferenceChanged,
           base::Unretained(this)));
-
   if (base::FeatureList::IsEnabled(ash::features::kPerUserMetrics)) {
     pref_change_registrar_.Add(
         metrics::prefs::kMetricsUserConsent,
@@ -76,15 +59,21 @@
   }
 
   // Send current state.
-  SendMetricsMode();
+  OnMetricsPreferenceChanged();
   SendBackupAndRestoreMode();
   SendLocationServicesMode();
 }
 
-ArcOptInPreferenceHandler::~ArcOptInPreferenceHandler() {}
+ArcOptInPreferenceHandler::~ArcOptInPreferenceHandler() = default;
 
 void ArcOptInPreferenceHandler::OnMetricsPreferenceChanged() {
-  SendMetricsMode();
+  auto* const device_settings_service = ash::DeviceSettingsService::Get();
+  DCHECK(device_settings_service);
+
+  device_settings_service->GetOwnershipStatusAsync(
+      base::IgnoreArgs<ash::DeviceSettingsService::OwnershipStatus>(
+          base::BindOnce(&ArcOptInPreferenceHandler::SendMetricsMode,
+                         weak_ptr_factory_.GetWeakPtr())));
 }
 
 void ArcOptInPreferenceHandler::OnBackupAndRestorePreferenceChanged() {
@@ -95,19 +84,24 @@
   SendLocationServicesMode();
 }
 
+void ArcOptInPreferenceHandler::EnableMetricsOnOwnershipKnown(
+    bool metrics_enabled) {
+  if (ShouldUpdateUserConsent()) {
+    EnableUserMetrics(metrics_enabled);
+  } else {
+    // Handles case in which device is either not owned or per-user is not
+    // enabled.
+    ash::StatsReportingController::Get()->SetEnabled(
+        ProfileManager::GetActiveUserProfile(), metrics_enabled);
+  }
+
+  DCHECK(enable_metrics_callback_);
+  std::move(enable_metrics_callback_).Run();
+}
+
 void ArcOptInPreferenceHandler::SendMetricsMode() {
   if (ShouldUpdateUserConsent()) {
-    auto* metrics_service = g_browser_process->metrics_service();
-    DCHECK(metrics_service);
-
-    absl::optional<bool> metrics_enabled =
-        g_browser_process->metrics_service()->GetCurrentUserMetricsConsent();
-
-    // No value means user is not eligible for per-user consent. This should be
-    // caught by ShouldUpdateUserConsent().
-    DCHECK(metrics_enabled.has_value());
-
-    observer_->OnMetricsModeChanged(*metrics_enabled,
+    observer_->OnMetricsModeChanged(GetUserMetrics(),
                                     IsMetricsReportingPolicyManaged());
   } else if (g_browser_process->local_state()) {
     bool enabled = ash::StatsReportingController::Get()->IsEnabled();
@@ -139,21 +133,18 @@
       pref_service_->IsManagedPreference(prefs::kArcLocationServiceEnabled));
 }
 
-void ArcOptInPreferenceHandler::EnableMetrics(bool is_enabled) {
-  if (ShouldUpdateUserConsent()) {
-    auto* metrics_service = g_browser_process->metrics_service();
-    DCHECK(metrics_service);
+void ArcOptInPreferenceHandler::EnableMetrics(bool is_enabled,
+                                              base::OnceClosure callback) {
+  auto* device_settings_service = ash::DeviceSettingsService::Get();
+  DCHECK(device_settings_service);
 
-    // If user is not eligible for per-user, this will no-op. See details at
-    // chrome/browser/metrics/per_user_state_manager_chromeos.h.
-    metrics_service->UpdateCurrentUserMetricsConsent(is_enabled);
-    return;
-  }
+  device_settings_service->GetOwnershipStatusAsync(
+      base::IgnoreArgs<ash::DeviceSettingsService::OwnershipStatus>(
+          base::BindOnce(
+              &ArcOptInPreferenceHandler::EnableMetricsOnOwnershipKnown,
+              weak_ptr_factory_.GetWeakPtr(), is_enabled)));
 
-  // Handles case in which device is either not owned or per-user is not
-  // enabled.
-  ash::StatsReportingController::Get()->SetEnabled(
-      ProfileManager::GetActiveUserProfile(), is_enabled);
+  enable_metrics_callback_ = std::move(callback);
 }
 
 void ArcOptInPreferenceHandler::EnableBackupRestore(bool is_enabled) {
@@ -164,4 +155,36 @@
   pref_service_->SetBoolean(prefs::kArcLocationServiceEnabled, is_enabled);
 }
 
+bool ArcOptInPreferenceHandler::ShouldUpdateUserConsent() {
+  // Return user consent should not be used if feature is disabled.
+  if (!base::FeatureList::IsEnabled(ash::features::kPerUserMetrics)) {
+    return false;
+  }
+
+  if (!metrics_service_->GetCurrentUserMetricsConsent().has_value()) {
+    return false;
+  }
+
+  // Per user metrics should be disabled if the device metrics was disabled by
+  // the owner.
+  return ash::StatsReportingController::Get()->IsEnabled();
+}
+
+void ArcOptInPreferenceHandler::EnableUserMetrics(bool is_enabled) {
+  // If user is not eligible for per-user, this will no-op. See details at
+  // chrome/browser/metrics/per_user_state_manager_chromeos.h.
+  metrics_service_->UpdateCurrentUserMetricsConsent(is_enabled);
+}
+
+bool ArcOptInPreferenceHandler::GetUserMetrics() {
+  absl::optional<bool> metrics_enabled =
+      metrics_service_->GetCurrentUserMetricsConsent();
+
+  // No value means user is not eligible for per-user consent. This should be
+  // caught by ShouldUpdateUserConsent().
+  DCHECK(metrics_enabled.has_value());
+
+  return *metrics_enabled;
+}
+
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/optin/arc_optin_preference_handler.h b/chrome/browser/ash/arc/optin/arc_optin_preference_handler.h
index d21f665..2e3561a 100644
--- a/chrome/browser/ash/arc/optin/arc_optin_preference_handler.h
+++ b/chrome/browser/ash/arc/optin/arc_optin_preference_handler.h
@@ -6,11 +6,16 @@
 #define CHROME_BROWSER_ASH_ARC_OPTIN_ARC_OPTIN_PREFERENCE_HANDLER_H_
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/settings/stats_reporting_controller.h"
 #include "components/prefs/pref_change_registrar.h"
 
 class PrefService;
 
+namespace metrics {
+class MetricsService;
+}
+
 namespace arc {
 
 class ArcOptInPreferenceHandlerObserver;
@@ -27,7 +32,8 @@
 class ArcOptInPreferenceHandler {
  public:
   ArcOptInPreferenceHandler(ArcOptInPreferenceHandlerObserver* observer,
-                            PrefService* pref_serive);
+                            PrefService* pref_service,
+                            metrics::MetricsService* metrics_service);
 
   ArcOptInPreferenceHandler(const ArcOptInPreferenceHandler&) = delete;
   ArcOptInPreferenceHandler& operator=(const ArcOptInPreferenceHandler&) =
@@ -37,7 +43,9 @@
 
   void Start();
 
-  void EnableMetrics(bool is_enabled);
+  // Enabling metrics happens asynchronously as it depends on device ownership.
+  // Once the update has been called, |callback| will be called.
+  void EnableMetrics(bool is_enabled, base::OnceClosure callback);
   void EnableBackupRestore(bool is_enabled);
   void EnableLocationService(bool is_enabled);
 
@@ -46,6 +54,16 @@
   void OnBackupAndRestorePreferenceChanged();
   void OnLocationServicePreferenceChanged();
 
+  // Helper functions to retrieve user metrics consent.
+  bool ShouldUpdateUserConsent();
+  bool GetUserMetrics();
+  void EnableUserMetrics(bool is_enabled);
+
+  // Helper method to update metrics asynchronously. Updating the correct
+  // metrics prefs depends on knowing ownership status, which may not be ready
+  // immediately.
+  void EnableMetricsOnOwnershipKnown(bool metrics_enabled);
+
   // Utilities on preference update.
   void SendMetricsMode();
   void SendBackupAndRestoreMode();
@@ -54,6 +72,9 @@
   // Unowned pointers.
   const raw_ptr<ArcOptInPreferenceHandlerObserver, ExperimentalAsh> observer_;
   const raw_ptr<PrefService, ExperimentalAsh> pref_service_;
+  const raw_ptr<metrics::MetricsService, ExperimentalAsh> metrics_service_;
+
+  base::OnceClosure enable_metrics_callback_;
 
   // Used to track metrics preference.
   PrefChangeRegistrar pref_local_change_registrar_;
@@ -61,6 +82,8 @@
   PrefChangeRegistrar pref_change_registrar_;
   // Metrics consent observer.
   base::CallbackListSubscription reporting_consent_subscription_;
+
+  base::WeakPtrFactory<ArcOptInPreferenceHandler> weak_ptr_factory_{this};
 };
 
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.cc b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.cc
index 5b3476c2..574bb33 100644
--- a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.cc
+++ b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.cc
@@ -12,10 +12,14 @@
 
 ArcTermsOfServiceDefaultNegotiator::ArcTermsOfServiceDefaultNegotiator(
     PrefService* pref_service,
-    ArcSupportHost* support_host)
-    : pref_service_(pref_service), support_host_(support_host) {
+    ArcSupportHost* support_host,
+    metrics::MetricsService* metrics_service)
+    : pref_service_(pref_service),
+      support_host_(support_host),
+      metrics_service_(metrics_service) {
   DCHECK(pref_service_);
   DCHECK(support_host_);
+  DCHECK(metrics_service_);
 }
 
 ArcTermsOfServiceDefaultNegotiator::~ArcTermsOfServiceDefaultNegotiator() {
@@ -24,8 +28,8 @@
 
 void ArcTermsOfServiceDefaultNegotiator::StartNegotiationImpl() {
   DCHECK(!preference_handler_);
-  preference_handler_ =
-      std::make_unique<ArcOptInPreferenceHandler>(this, pref_service_);
+  preference_handler_ = std::make_unique<ArcOptInPreferenceHandler>(
+      this, pref_service_, metrics_service_);
   // This automatically updates all preferences.
   preference_handler_->Start();
 
@@ -51,12 +55,15 @@
   support_host_->SetTermsOfServiceDelegate(nullptr);
 
   // Update the preferences with the value passed from UI.
-  preference_handler_->EnableMetrics(is_metrics_enabled);
   preference_handler_->EnableBackupRestore(is_backup_and_restore_enabled);
   preference_handler_->EnableLocationService(is_location_service_enabled);
-  preference_handler_.reset();
 
-  ReportResult(true);
+  // This should be called last since the callback resets |preference_handler_|
+  // and makes the callback that terms of service has successfully negotiated.
+  preference_handler_->EnableMetrics(
+      is_metrics_enabled,
+      base::BindOnce(&ArcTermsOfServiceDefaultNegotiator::OnMetricsPrefsUpdated,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ArcTermsOfServiceDefaultNegotiator::OnTermsRetryClicked() {
@@ -80,4 +87,9 @@
   support_host_->SetLocationServicesPreferenceCheckbox(enabled, managed);
 }
 
+void ArcTermsOfServiceDefaultNegotiator::OnMetricsPrefsUpdated() {
+  preference_handler_.reset();
+  ReportResult(true);
+}
+
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.h b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.h
index 5385d70..9c31a63 100644
--- a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.h
+++ b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.h
@@ -15,6 +15,12 @@
 
 class PrefService;
 
+namespace metrics {
+
+class MetricsService;
+
+}
+
 namespace arc {
 
 class ArcOptInPreferenceHandler;
@@ -26,7 +32,8 @@
       public ArcOptInPreferenceHandlerObserver {
  public:
   ArcTermsOfServiceDefaultNegotiator(PrefService* pref_service,
-                                     ArcSupportHost* support_host);
+                                     ArcSupportHost* support_host,
+                                     metrics::MetricsService* metrics_service);
 
   ArcTermsOfServiceDefaultNegotiator(
       const ArcTermsOfServiceDefaultNegotiator&) = delete;
@@ -48,6 +55,10 @@
   void OnBackupAndRestoreModeChanged(bool enabled, bool managed) override;
   void OnLocationServicesModeChanged(bool enabled, bool managed) override;
 
+  // Callback when metrics prefs have successfully been updated by
+  // |preference_handler_|.
+  void OnMetricsPrefsUpdated();
+
   // ArcTermsOfServiceNegotiator:
   // Shows "Terms of service" page on ARC support Chrome App.
   void StartNegotiationImpl() override;
@@ -56,7 +67,12 @@
   // Owned by ArcSessionManager.
   const raw_ptr<ArcSupportHost, ExperimentalAsh> support_host_;
 
+  const raw_ptr<metrics::MetricsService, ExperimentalAsh> metrics_service_;
+
   std::unique_ptr<ArcOptInPreferenceHandler> preference_handler_;
+
+  base::WeakPtrFactory<ArcTermsOfServiceDefaultNegotiator> weak_ptr_factory_{
+      this};
 };
 
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc
index b323a4b..8651ef9 100644
--- a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc
+++ b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc
@@ -12,10 +12,15 @@
 #include "base/functional/bind.h"
 #include "base/hash/sha1.h"
 #include "base/run_loop.h"
+#include "base/test/bind.h"
 #include "base/values.h"
 #include "chrome/browser/ash/arc/arc_support_host.h"
 #include "chrome/browser/ash/arc/extensions/fake_arc_support.h"
+#include "chrome/browser/ash/arc/optin/arc_optin_preference_handler.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
+#include "chrome/browser/ash/policy/core/device_policy_builder.h"
+#include "chrome/browser/ash/settings/device_settings_test_helper.h"
 #include "chrome/browser/ash/settings/stats_reporting_controller.h"
 #include "chrome/browser/consent_auditor/consent_auditor_factory.h"
 #include "chrome/browser/consent_auditor/consent_auditor_test_utils.h"
@@ -24,7 +29,13 @@
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
 #include "components/consent_auditor/fake_consent_auditor.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/test/test_enabled_state_provider.h"
+#include "components/metrics/test/test_metrics_service_client.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/testing_pref_store.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -32,12 +43,15 @@
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+
+using ::testing::_;
 using ::testing::Matches;
 using ::testing::Mock;
-using ::testing::_;
 
 using consent_auditor::ArcBackupAndRestoreConsentEq;
 using consent_auditor::ArcGoogleLocationServiceConsentEq;
@@ -51,12 +65,43 @@
     sync_pb::UserConsentTypes::ArcPlayTermsOfServiceConsent;
 using sync_pb::UserConsentTypes;
 
+using OwnershipStatus = ash::DeviceSettingsService::OwnershipStatus;
+
+class TestUserMetricsServiceClient
+    : public ::metrics::TestMetricsServiceClient {
+ public:
+  absl::optional<bool> GetCurrentUserMetricsConsent() const override {
+    if (should_use_user_consent_) {
+      return current_user_metrics_consent_;
+    }
+    return absl::nullopt;
+  }
+
+  void UpdateCurrentUserMetricsConsent(bool metrics_consent) override {
+    current_user_metrics_consent_ = metrics_consent;
+  }
+
+  void SetShouldUseUserConsent(bool should_use_user_consent) {
+    should_use_user_consent_ = should_use_user_consent;
+  }
+
+ private:
+  bool should_use_user_consent_ = false;
+  bool current_user_metrics_consent_ = false;
+};
+
+}  // namespace
+
 namespace arc {
 
 class ArcTermsOfServiceDefaultNegotiatorTest
     : public BrowserWithTestWindowTest {
  public:
-  ArcTermsOfServiceDefaultNegotiatorTest() = default;
+  ArcTermsOfServiceDefaultNegotiatorTest()
+      : owner_key_util_(new ownership::MockOwnerKeyUtil()) {
+    ::ash::OwnerSettingsServiceAshFactory::GetInstance()
+        ->SetOwnerKeyUtilForTesting(owner_key_util_);
+  }
 
   ArcTermsOfServiceDefaultNegotiatorTest(
       const ArcTermsOfServiceDefaultNegotiatorTest&) = delete;
@@ -67,6 +112,27 @@
 
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
+
+    ::ash::DeviceSettingsService::Get()->SetSessionManager(
+        &session_manager_client_, owner_key_util_);
+
+    // MetricsService.
+    metrics::MetricsService::RegisterPrefs(local_state_.registry());
+    test_enabled_state_provider_ =
+        std::make_unique<metrics::TestEnabledStateProvider>(true, true);
+    test_metrics_state_manager_ = metrics::MetricsStateManager::Create(
+        &local_state_, test_enabled_state_provider_.get(), std::wstring(),
+        base::FilePath());
+    test_metrics_service_client_ =
+        std::make_unique<TestUserMetricsServiceClient>();
+    test_metrics_service_ = std::make_unique<metrics::MetricsService>(
+        test_metrics_state_manager_.get(), test_metrics_service_client_.get(),
+        &local_state_);
+
+    // Needs to be set for metrics service.
+    base::SetRecordActionTaskRunner(
+        task_environment()->GetMainThreadTaskRunner());
+
     user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
         std::make_unique<ash::FakeChromeUserManager>());
     signin::MakePrimaryAccountAvailable(
@@ -80,7 +146,7 @@
     support_host_ = std::make_unique<ArcSupportHost>(profile());
     fake_arc_support_ = std::make_unique<FakeArcSupport>(support_host_.get());
     negotiator_ = std::make_unique<ArcTermsOfServiceDefaultNegotiator>(
-        profile()->GetPrefs(), support_host());
+        profile()->GetPrefs(), support_host(), test_metrics_service_.get());
   }
 
   void TearDown() override {
@@ -88,14 +154,36 @@
     fake_arc_support_.reset();
     support_host_.reset();
     user_manager_enabler_.reset();
+    owner_key_util_->Clear();
 
+    test_metrics_service_.reset();
+    test_metrics_service_client_.reset();
+    test_metrics_state_manager_.reset();
+    test_enabled_state_provider_.reset();
+
+    ::ash::DeviceSettingsService::Get()->UnsetSessionManager();
     ash::StatsReportingController::Shutdown();
     BrowserWithTestWindowTest::TearDown();
   }
 
+  void LoadOwnershipStatus() {
+    owner_key_util_->SetPublicKeyFromPrivateKey(
+        *device_policy_.GetSigningKey());
+
+    content::RunAllTasksUntilIdle();
+  }
+
   ArcSupportHost* support_host() { return support_host_.get(); }
   FakeArcSupport* fake_arc_support() { return fake_arc_support_.get(); }
   ArcTermsOfServiceNegotiator* negotiator() { return negotiator_.get(); }
+  TestUserMetricsServiceClient* metrics_service_client() {
+    return test_metrics_service_client_.get();
+  }
+
+  ash::FakeChromeUserManager* user_manager() {
+    return static_cast<ash::FakeChromeUserManager*>(
+        user_manager::UserManager::Get());
+  }
 
   consent_auditor::FakeConsentAuditor* consent_auditor() {
     return static_cast<consent_auditor::FakeConsentAuditor*>(
@@ -108,18 +196,60 @@
         .account_id;
   }
 
+  bool GetUserMetricsState() {
+    return *metrics_service_client()->GetCurrentUserMetricsConsent();
+  }
+
   // BrowserWithTestWindowTest:
   TestingProfile::TestingFactories GetTestingFactories() override {
     return {{ConsentAuditorFactory::GetInstance(),
              base::BindRepeating(&BuildFakeConsentAuditor)}};
   }
 
- private:
+ protected:
+  policy::DevicePolicyBuilder device_policy_;
+  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_;
+  ::ash::FakeSessionManagerClient session_manager_client_;
+
   TestingPrefServiceSimple local_state_;
+
+  // MetricsService.
+  std::unique_ptr<metrics::MetricsStateManager> test_metrics_state_manager_;
+  std::unique_ptr<TestUserMetricsServiceClient> test_metrics_service_client_;
+  std::unique_ptr<metrics::TestEnabledStateProvider>
+      test_enabled_state_provider_;
+  std::unique_ptr<metrics::MetricsService> test_metrics_service_;
+
   std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
   std::unique_ptr<ArcSupportHost> support_host_;
   std::unique_ptr<FakeArcSupport> fake_arc_support_;
-  std::unique_ptr<ArcTermsOfServiceNegotiator> negotiator_;
+  std::unique_ptr<ArcTermsOfServiceDefaultNegotiator> negotiator_;
+};
+
+class ArcTermsOfServiceDefaultNegotiatorForNonOwnerTest
+    : public ArcTermsOfServiceDefaultNegotiatorTest {
+ protected:
+  void SetUp() override {
+    device_policy_.SetDefaultNewSigningKey();
+    owner_key_util_->SetPublicKeyFromPrivateKey(
+        *device_policy_.GetNewSigningKey());
+
+    device_policy_.payload().mutable_metrics_enabled()->set_metrics_enabled(
+        true);
+    device_policy_.Build();
+    session_manager_client_.set_device_policy(device_policy_.GetBlob());
+
+    ArcTermsOfServiceDefaultNegotiatorTest::SetUp();
+  }
+
+  // BrowserWithTestWindowTest:
+  TestingProfile* CreateProfile() override {
+    const std::string name = "test2@example.com";
+    const AccountId account_id(AccountId::FromUserEmail(name));
+    user_manager()->AddUser(account_id);
+    user_manager()->LoginUser(account_id);
+    return profile_manager()->CreateTestingProfile(name);
+  }
 };
 
 namespace {
@@ -259,6 +389,10 @@
   // Click the "AGREE" button so that the callback should be invoked
   // with |agreed| = true.
   fake_arc_support()->ClickAgreeButton();
+
+  // Wait until async calls are all completed, which is triggered by ownership
+  // status being loaded.
+  LoadOwnershipStatus();
   EXPECT_EQ(status, Status::ACCEPTED);
 
   // Make sure preference values are now updated.
@@ -327,6 +461,10 @@
   // Click the "AGREE" button so that the callback should be invoked
   // with |agreed| = true.
   fake_arc_support()->ClickAgreeButton();
+
+  // Wait until async calls are all completed, which is triggered by ownership
+  // status being loaded.
+  LoadOwnershipStatus();
   EXPECT_EQ(status, Status::ACCEPTED);
 
   // Make sure preference values are now updated.
@@ -336,6 +474,81 @@
       profile()->GetPrefs()->GetBoolean(prefs::kArcLocationServiceEnabled));
 }
 
+TEST_F(ArcTermsOfServiceDefaultNegotiatorTest, AcceptMetricsNoOwner) {
+  // Show Terms of service page.
+  Status status = Status::PENDING;
+  negotiator()->StartNegotiation(UpdateStatusCallback(&status));
+
+  // TERMS page should be shown.
+  EXPECT_EQ(status, Status::PENDING);
+  EXPECT_EQ(fake_arc_support()->ui_page(), ArcSupportHost::UIPage::TERMS);
+
+  // Emulate showing of a ToS page with a hard-coded ID.
+  fake_arc_support()->set_metrics_mode(false);
+
+  bool expected_metrics_state = true;
+
+  // Check the preference related checkboxes.
+  fake_arc_support()->set_metrics_mode(expected_metrics_state);
+
+  // Click the "AGREE" button so that the callback should be invoked
+  // with |agreed| = true.
+  fake_arc_support()->ClickAgreeButton();
+
+  // Owners status has not been loaded yet. Changes should not be propagated.
+  EXPECT_NE(expected_metrics_state,
+            ash::StatsReportingController::Get()->IsEnabled());
+
+  // Check owners opt-in once ownership status known.
+  LoadOwnershipStatus();
+  EXPECT_EQ(status, Status::ACCEPTED);
+  EXPECT_EQ(ash::DeviceSettingsService::Get()->GetOwnershipStatus(),
+            OwnershipStatus::OWNERSHIP_NONE);
+  EXPECT_EQ(expected_metrics_state,
+            ash::StatsReportingController::Get()->IsEnabled());
+}
+
+TEST_F(ArcTermsOfServiceDefaultNegotiatorForNonOwnerTest,
+       AcceptMetricsUserOptIn) {
+  // Show Terms of service page.
+  Status status = Status::PENDING;
+  negotiator()->StartNegotiation(UpdateStatusCallback(&status));
+
+  // Setup metrics service to use user metrics.
+  metrics_service_client()->SetShouldUseUserConsent(true);
+
+  // TERMS page should be shown.
+  EXPECT_EQ(status, Status::PENDING);
+  EXPECT_EQ(fake_arc_support()->ui_page(), ArcSupportHost::UIPage::TERMS);
+
+  // Load ownership and enable metrics by the owner. If owner has opted out of
+  // metrics, per-user metrics opt-in is not allowed. Ensure that the ownership
+  // is probably setup.
+  LoadOwnershipStatus();
+  EXPECT_EQ(ash::DeviceSettingsService::Get()->GetOwnershipStatus(),
+            OwnershipStatus::OWNERSHIP_TAKEN);
+  EXPECT_TRUE(ash::StatsReportingController::Get()->IsEnabled());
+  EXPECT_FALSE(user_manager::UserManager::Get()->IsCurrentUserOwner());
+
+  // Emulate showing of a ToS page with a hard-coded ID.
+  fake_arc_support()->set_metrics_mode(false);
+
+  bool expected_metrics_state = true;
+
+  // Check the preference related checkboxes.
+  fake_arc_support()->set_metrics_mode(expected_metrics_state);
+
+  // Click the "AGREE" button so that the callback should be invoked
+  // with |agreed| = true.
+  fake_arc_support()->ClickAgreeButton();
+
+  // Wait for async calls to finish.
+  content::RunAllTasksUntilIdle();
+
+  EXPECT_EQ(status, Status::ACCEPTED);
+  EXPECT_EQ(expected_metrics_state, GetUserMetricsState());
+}
+
 TEST_F(ArcTermsOfServiceDefaultNegotiatorTest, AcceptWithManagedToS) {
   consent_auditor::FakeConsentAuditor* auditor = consent_auditor();
   Mock::VerifyAndClearExpectations(auditor);
@@ -380,9 +593,13 @@
   // Click the "AGREE" button so that the callback should be invoked
   // with |agreed| = true.
   fake_arc_support()->ClickAgreeButton();
-  EXPECT_EQ(status, Status::ACCEPTED);
+
+  // Wait until async calls are all completed, which is triggered by ownership
+  // status being loaded.
+  LoadOwnershipStatus();
 
   // Make sure preference values are now updated.
+  EXPECT_EQ(status, Status::ACCEPTED);
   EXPECT_TRUE(
       profile()->GetPrefs()->GetBoolean(prefs::kArcBackupRestoreEnabled));
   EXPECT_TRUE(
diff --git a/chrome/browser/ash/arc/session/arc_requirement_checker.cc b/chrome/browser/ash/arc/session/arc_requirement_checker.cc
index ab7dfbc..ec0ff76 100644
--- a/chrome/browser/ash/arc/session/arc_requirement_checker.cc
+++ b/chrome/browser/ash/arc/session/arc_requirement_checker.cc
@@ -158,7 +158,8 @@
     VLOG(1) << "Use default negotiator.";
     terms_of_service_negotiator_ =
         std::make_unique<ArcTermsOfServiceDefaultNegotiator>(
-            profile_->GetPrefs(), support_host_);
+            profile_->GetPrefs(), support_host_,
+            g_browser_process->metrics_service());
   } else {
     DCHECK(!g_ui_enabled) << "Negotiator is not created on production.";
     return;
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index 3bf57a3d..386d910 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -435,6 +435,7 @@
   ArcFileSystemMounter::EnsureFactoryBuilt();
   ArcFileSystemOperationRunner::EnsureFactoryBuilt();
   ArcFileSystemWatcherService::EnsureFactoryBuilt();
+  ArcIdleManager::EnsureFactoryBuilt();
   ArcIioSensorBridge::EnsureFactoryBuilt();
   ArcImeService::EnsureFactoryBuilt();
   ArcInitialOptInNotifier::EnsureFactoryBuilt();
diff --git a/chrome/browser/ash/arc/session/arc_session_manager_browsertest.cc b/chrome/browser/ash/arc/session/arc_session_manager_browsertest.cc
index d64969e33..ef68b7b 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager_browsertest.cc
+++ b/chrome/browser/ash/arc/session/arc_session_manager_browsertest.cc
@@ -26,8 +26,8 @@
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/ash/arc/test/arc_data_removed_waiter.h"
 #include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/certificate_provider/certificate_provider_service.h"
diff --git a/chrome/browser/ash/arc/vmm/arc_system_state_observation.cc b/chrome/browser/ash/arc/vmm/arc_system_state_observation.cc
index 98917e0..9bf7335b 100644
--- a/chrome/browser/ash/arc/vmm/arc_system_state_observation.cc
+++ b/chrome/browser/ash/arc/vmm/arc_system_state_observation.cc
@@ -21,6 +21,8 @@
 
   // Observe ARC window in ash.
   AddObserver(std::make_unique<ArcWindowObserver>());
+
+  StartObservers();
 }
 
 ArcSystemStateObservation::~ArcSystemStateObservation() = default;
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator.h b/chrome/browser/ash/crosapi/browser_data_back_migrator.h
index f540478..a748d85 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator.h
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator.h
@@ -7,6 +7,7 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
diff --git a/chrome/browser/ash/crosapi/crosapi_util.cc b/chrome/browser/ash/crosapi/crosapi_util.cc
index 28e0870..7f4a5e4 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.cc
+++ b/chrome/browser/ash/crosapi/crosapi_util.cc
@@ -451,20 +451,7 @@
 
 InitialBrowserAction::InitialBrowserAction(
     crosapi::mojom::InitialBrowserAction action)
-    : action(action) {
-  // kOpnWindowWIthUrls should take the argument, so the ctor below should be
-  // used.
-  DCHECK_NE(action, crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls);
-}
-
-InitialBrowserAction::InitialBrowserAction(
-    crosapi::mojom::InitialBrowserAction action,
-    std::vector<GURL> urls,
-    crosapi::mojom::OpenUrlFrom from)
-    : action(action), urls(std::move(urls)), from(from) {
-  // Currently, only kOpenWindowWithUrls can take the URLs as its argument.
-  DCHECK_EQ(action, crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls);
-}
+    : action(action) {}
 
 InitialBrowserAction::InitialBrowserAction(InitialBrowserAction&&) = default;
 InitialBrowserAction& InitialBrowserAction::operator=(InitialBrowserAction&&) =
@@ -650,12 +637,6 @@
       environment_provider->GetLastPolicyFetchAttemptTimestamp().ToTimeT();
 
   params->initial_browser_action = initial_browser_action.action;
-  if (initial_browser_action.action ==
-      crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls) {
-    params->startup_urls = std::move(initial_browser_action.urls);
-    params->startup_urls_from = initial_browser_action.from;
-  }
-
   params->web_apps_enabled = web_app::IsWebAppsCrosapiEnabled();
   params->standalone_browser_is_primary = IsLacrosPrimaryBrowser();
 
diff --git a/chrome/browser/ash/crosapi/crosapi_util.h b/chrome/browser/ash/crosapi/crosapi_util.h
index 8f3b26a..a8c9c76 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.h
+++ b/chrome/browser/ash/crosapi/crosapi_util.h
@@ -32,9 +32,6 @@
 // Represents how to launch Lacros Chrome.
 struct InitialBrowserAction {
   explicit InitialBrowserAction(crosapi::mojom::InitialBrowserAction action);
-  InitialBrowserAction(crosapi::mojom::InitialBrowserAction action,
-                       std::vector<GURL> urls,
-                       crosapi::mojom::OpenUrlFrom from);
   InitialBrowserAction(InitialBrowserAction&&);
   InitialBrowserAction& operator=(InitialBrowserAction&&);
   ~InitialBrowserAction();
diff --git a/chrome/browser/ash/crosapi/print_job_info_idl_conversions.cc b/chrome/browser/ash/crosapi/print_job_info_idl_conversions.cc
index dbddbf5a..8edf4e2 100644
--- a/chrome/browser/ash/crosapi/print_job_info_idl_conversions.cc
+++ b/chrome/browser/ash/crosapi/print_job_info_idl_conversions.cc
@@ -139,6 +139,8 @@
     case proto::PrintJobInfo_PrinterErrorCode_UNKNOWN_ERROR:
     case proto::PrintJobInfo_PrinterErrorCode_CLIENT_UNAUTHORIZED:
       return api::printing::PRINTER_STATUS_GENERIC_ISSUE;
+    case proto::PrintJobInfo_PrinterErrorCode_EXPIRED_CERTIFICATE:
+      return api::printing::PRINTER_STATUS_EXPIRED_CERTIFICATE;
     case proto::
         PrintJobInfo_PrinterErrorCode_PrintJobInfo_PrinterErrorCode_INT_MIN_SENTINEL_DO_NOT_USE_:
     case proto::
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
index 6090f39..a8a816a 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -3947,6 +3947,7 @@
     result_item.status = GetShelfItemStatus(item.status);
     result_item.shows_tooltip = item.shows_tooltip;
     result_item.pinned_by_policy = item.pinned_by_policy;
+    result_item.pin_state_forced_by_type = item.pin_state_forced_by_type;
     result_item.has_notification = item.has_notification;
     result_items.emplace_back(std::move(result_item));
   }
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc b/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc
index 76b83373..6d53ae0 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc
@@ -55,6 +55,7 @@
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/file_manager/restore_io_task.h"
 #include "chrome/browser/ash/file_manager/restore_to_destination_io_task.h"
+#include "chrome/browser/ash/file_manager/trash_common_util.h"
 #include "chrome/browser/ash/file_manager/trash_io_task.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
 #include "chrome/browser/ash/file_manager/zip_io_task.h"
@@ -1413,10 +1414,21 @@
     return RespondNow(Error("Cannot convert category to file type"));
   }
 
+  std::vector<base::FilePath> excluded_paths;
+  if (file_manager::trash::IsTrashEnabledForProfile((profile))) {
+    auto enabled_trash_locations =
+        file_manager::trash::GenerateEnabledTrashLocationsForProfile(
+            profile, /*base_path=*/base::FilePath());
+    for (const auto& it : enabled_trash_locations) {
+      excluded_paths.emplace_back(
+          it.first.Append(it.second.relative_folder_path));
+    }
+  }
+
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
       base::BindOnce(
-          &SearchByPattern, root_path, search_params.query,
+          &SearchByPattern, root_path, excluded_paths, search_params.query,
           base::Time::FromJsTime(search_params.timestamp), file_type,
           base::internal::checked_cast<size_t>(search_params.max_results)),
       base::BindOnce(
diff --git a/chrome/browser/ash/extensions/file_manager/search_by_pattern.cc b/chrome/browser/ash/extensions/file_manager/search_by_pattern.cc
index 90168754..576a163a 100644
--- a/chrome/browser/ash/extensions/file_manager/search_by_pattern.cc
+++ b/chrome/browser/ash/extensions/file_manager/search_by_pattern.cc
@@ -43,6 +43,7 @@
 
 std::vector<std::pair<base::FilePath, bool>> SearchByPattern(
     const base::FilePath& root,
+    const std::vector<base::FilePath>& excluded_paths,
     const std::string& query,
     const base::Time& min_timestamp,
     ash::RecentSource::FileType file_type,
@@ -63,6 +64,15 @@
     if (!ash::RecentDiskSource::MatchesFileType(path, file_type)) {
       continue;
     }
+    // Reject files that have path in excluded paths.
+    if (base::ranges::any_of(
+            excluded_paths, [&path](const base::FilePath& excluded_path) {
+              DCHECK(!path.EndsWithSeparator());
+              DCHECK(!excluded_path.EndsWithSeparator());
+              return excluded_path == path || excluded_path.IsParent(path);
+            })) {
+      continue;
+    }
     if (base::StartsWith(path.BaseName().value(), query,
                          base::CompareCase::INSENSITIVE_ASCII)) {
       prefix_matches.emplace_back(path, enumerator.GetInfo().IsDirectory());
diff --git a/chrome/browser/ash/extensions/file_manager/search_by_pattern.h b/chrome/browser/ash/extensions/file_manager/search_by_pattern.h
index bec667e..abce6ba 100644
--- a/chrome/browser/ash/extensions/file_manager/search_by_pattern.h
+++ b/chrome/browser/ash/extensions/file_manager/search_by_pattern.h
@@ -22,13 +22,15 @@
 std::string CreateFnmatchQuery(const std::string& query);
 
 // For the given root it attempts to find files that have name matching the
-// given pattern, are modified after the given min_timestamp and are of the
+// given pattern, are not in folders whose paths are anywhere on the
+// excluded_paths, are modified after the given min_timestamp and are of the
 // given file type. This function will return up to max_results. The results are
 // returned as a vector of pairs, with the first element of the pair being the
 // matched file path, and the second indicating if the file is a directory or a
 // plain file.
 std::vector<std::pair<base::FilePath, bool>> SearchByPattern(
     const base::FilePath& root,
+    const std::vector<base::FilePath>& excluded_paths,
     const std::string& query,
     const base::Time& min_timestamp,
     ash::RecentSource::FileType file_type,
diff --git a/chrome/browser/ash/extensions/file_manager/search_by_pattern_fuzzer.cc b/chrome/browser/ash/extensions/file_manager/search_by_pattern_fuzzer.cc
index 9407624..4527034 100644
--- a/chrome/browser/ash/extensions/file_manager/search_by_pattern_fuzzer.cc
+++ b/chrome/browser/ash/extensions/file_manager/search_by_pattern_fuzzer.cc
@@ -52,9 +52,10 @@
   base::Time min_modified_time = base::Time::UnixEpoch();
   std::string query = std::string(reinterpret_cast<const char*>(data), size);
 
-  // Searching by fuzzed query. Using most broad parameters, accepting files of
-  // any time with modified date after 1 Jan 1970.
-  extensions::SearchByPattern(test_dir, query, min_modified_time,
+  // Searching by fuzzed query. Using most broad parameters, excluding no paths,
+  // accepting files of any time with modified date after 1 Jan 1970.
+  std::vector<base::FilePath> excluded_path;
+  extensions::SearchByPattern(test_dir, excluded_path, query, min_modified_time,
                               ash::RecentSource::FileType::kAll,
                               file_names.size());
   return 0;
diff --git a/chrome/browser/ash/extensions/file_manager/search_by_pattern_unittest.cc b/chrome/browser/ash/extensions/file_manager/search_by_pattern_unittest.cc
index fa5ef9f..e006b67 100644
--- a/chrome/browser/ash/extensions/file_manager/search_by_pattern_unittest.cc
+++ b/chrome/browser/ash/extensions/file_manager/search_by_pattern_unittest.cc
@@ -48,9 +48,12 @@
 TEST(SearchByPatternTest, SearchByPattern) {
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  auto trashDirPath = temp_dir.GetPath().Append(".Trash");
+  EXPECT_TRUE(base::CreateDirectory(trashDirPath));
 
   std::vector<base::FilePath> test_files = {
       base::FilePath::FromASCII("aaa"),
+      base::FilePath::FromASCII(".Trash/trash_file"),
   };
   auto created_files = SetUpTestFiles(temp_dir, test_files);
 
@@ -58,25 +61,34 @@
   ASSERT_TRUE(
       base::Time::FromString("1 Jan 1970 00:00 UTC", &min_modified_time));
 
+  std::vector<base::FilePath> excluded_paths = {trashDirPath};
+
   auto found_with_a =
-      SearchByPattern(temp_dir.GetPath(), "a", min_modified_time,
-                      ash::RecentSource::FileType::kAll, 1);
+      SearchByPattern(temp_dir.GetPath(), excluded_paths, "a",
+                      min_modified_time, ash::RecentSource::FileType::kAll, 3);
   EXPECT_THAT(found_with_a, ElementsAre(created_files[0]));
 
   auto found_with_b =
-      SearchByPattern(temp_dir.GetPath(), "b", min_modified_time,
-                      ash::RecentSource::FileType::kAll, 1);
+      SearchByPattern(temp_dir.GetPath(), excluded_paths, "b",
+                      min_modified_time, ash::RecentSource::FileType::kAll, 3);
   EXPECT_THAT(found_with_b, ElementsAre());
 
   auto found_with_empty =
-      SearchByPattern(temp_dir.GetPath(), "", min_modified_time,
-                      ash::RecentSource::FileType::kAll, 1);
+      SearchByPattern(temp_dir.GetPath(), excluded_paths, "", min_modified_time,
+                      ash::RecentSource::FileType::kAll, 3);
   EXPECT_THAT(found_with_empty, ElementsAre(created_files[0]));
 
   auto found_with_asterisk =
-      SearchByPattern(temp_dir.GetPath(), "", min_modified_time,
-                      ash::RecentSource::FileType::kAll, 1);
+      SearchByPattern(temp_dir.GetPath(), excluded_paths, "", min_modified_time,
+                      ash::RecentSource::FileType::kAll, 3);
   EXPECT_THAT(found_with_asterisk, ElementsAre(created_files[0]));
+
+  auto found_without_exclusions =
+      SearchByPattern(temp_dir.GetPath(), {}, "a", min_modified_time,
+                      ash::RecentSource::FileType::kAll, 10);
+  EXPECT_THAT(found_without_exclusions,
+              ElementsAre(created_files[0], std::make_pair(trashDirPath, true),
+                          created_files[1]));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/ash/file_manager/copy_or_move_io_task_impl.cc b/chrome/browser/ash/file_manager/copy_or_move_io_task_impl.cc
index 8fb09a1..fbc1a3c 100644
--- a/chrome/browser/ash/file_manager/copy_or_move_io_task_impl.cc
+++ b/chrome/browser/ash/file_manager/copy_or_move_io_task_impl.cc
@@ -188,6 +188,15 @@
   progress_callback_ = std::move(progress_callback);
   complete_callback_ = std::move(complete_callback);
 
+  if (progress_->sources.size() == 0) {
+    Complete(State::kSuccess);
+    return;
+  }
+
+  VerifyTransfer();
+}
+
+void CopyOrMoveIOTaskImpl::VerifyTransfer() {
   // TODO(b/280947989) remove this code once Multi-user sign-in is deprecated.
   // Prevent files being copied or moved to ODFS if there is a managed user
   // present amongst other logged in users. Ensures managed user's files can't
@@ -205,16 +214,6 @@
     }
   }
 
-  if (progress_->sources.size() == 0) {
-    Complete(State::kSuccess);
-    return;
-  }
-
-  VerifyTransfer();
-}
-
-void CopyOrMoveIOTaskImpl::VerifyTransfer() {
-  // No checks, just start the transfer.
   StartTransfer();
 }
 
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index 786f9425..856192f 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -2169,6 +2169,7 @@
         TestCase("selectionPath").EnableSearchV2(),
         TestCase("searchHierarchy").EnableSearchV2(),
         TestCase("hideSearchInTrash").EnableSearchV2(),
+        TestCase("searchTrashedFiles").EnableSearchV2(),
         TestCase("matchDriveFilesByName").EnableSearchV2()
         // TODO(b/189173190): Enable
         // TestCase("searchQueryLaunchParam")
diff --git a/chrome/browser/ash/file_manager/uma_enums.gen.cc b/chrome/browser/ash/file_manager/uma_enums.gen.cc
new file mode 100644
index 0000000..8d81871
--- /dev/null
+++ b/chrome/browser/ash/file_manager/uma_enums.gen.cc
@@ -0,0 +1,52 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/file_manager/uma_enums.gen.h"
+
+#include <algorithm>
+
+#include "base/strings/string_util.h"
+
+// File generated by //ui/file_manager/base/gn/uma_enums_generate.py.
+namespace file_manager::file_tasks {
+
+ViewFileType GetViewFileType(const base::FilePath& path) {
+  static constexpr auto kViewFileTypes = {
+      "other",     ".3ga",         ".3gp",
+      ".aac",      ".alac",        ".asf",
+      ".avi",      ".bmp",         ".csv",
+      ".doc",      ".docx",        ".flac",
+      ".gif",      ".jpeg",        ".jpg",
+      ".log",      ".m3u",         ".m3u8",
+      ".m4a",      ".m4v",         ".mid",
+      ".mkv",      ".mov",         ".mp3",
+      ".mp4",      ".mpg",         ".odf",
+      ".odp",      ".ods",         ".odt",
+      ".oga",      ".ogg",         ".ogv",
+      ".pdf",      ".png",         ".ppt",
+      ".pptx",     ".ra",          ".ram",
+      ".rar",      ".rm",          ".rtf",
+      ".wav",      ".webm",        ".webp",
+      ".wma",      ".wmv",         ".xls",
+      ".xlsx",     ".crdownload",  ".crx",
+      ".dmg",      ".exe",         ".html",
+      ".htm",      ".jar",         ".ps",
+      ".torrent",  ".txt",         ".zip",
+      "directory", "no extension", "unknown extension",
+      ".mhtml",    ".gdoc",        ".gsheet",
+      ".gslides",  ".arw",         ".cr2",
+      ".dng",      ".nef",         ".nrw",
+      ".orf",      ".raf",         ".rw2",
+      ".tini",
+  };
+
+  auto* const* it = std::find(kViewFileTypes.begin(), kViewFileTypes.end(),
+                              base::ToLowerASCII(path.FinalExtension()));
+  if (it != kViewFileTypes.end()) {
+    return static_cast<ViewFileType>(std::distance(kViewFileTypes.begin(), it));
+  }
+  return ViewFileType::kOther;
+}
+
+}  // namespace file_manager::file_tasks
diff --git a/chrome/browser/ash/file_manager/uma_enums.gen.h b/chrome/browser/ash/file_manager/uma_enums.gen.h
new file mode 100644
index 0000000..b60d1a0
--- /dev/null
+++ b/chrome/browser/ash/file_manager/uma_enums.gen.h
@@ -0,0 +1,100 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_FILE_MANAGER_UMA_ENUMS_GEN_H_
+#define CHROME_BROWSER_ASH_FILE_MANAGER_UMA_ENUMS_GEN_H_
+
+#include "base/files/file_path.h"
+
+// File generated by //ui/file_manager/base/gn/uma_enums_generate.py.
+namespace file_manager::file_tasks {
+
+/**
+ * List of file extensions to record in UMA, from enums.xml ViewFileType.
+ */
+enum class ViewFileType {
+  kOther,
+  k3Ga,
+  k3Gp,
+  kAac,
+  kAlac,
+  kAsf,
+  kAvi,
+  kBmp,
+  kCsv,
+  kDoc,
+  kDocx,
+  kFlac,
+  kGif,
+  kJpeg,
+  kJpg,
+  kLog,
+  kM3U,
+  kM3U8,
+  kM4A,
+  kM4V,
+  kMid,
+  kMkv,
+  kMov,
+  kMp3,
+  kMp4,
+  kMpg,
+  kOdf,
+  kOdp,
+  kOds,
+  kOdt,
+  kOga,
+  kOgg,
+  kOgv,
+  kPdf,
+  kPng,
+  kPpt,
+  kPptx,
+  kRa,
+  kRam,
+  kRar,
+  kRm,
+  kRtf,
+  kWav,
+  kWebm,
+  kWebp,
+  kWma,
+  kWmv,
+  kXls,
+  kXlsx,
+  kCrdownload,
+  kCrx,
+  kDmg,
+  kExe,
+  kHtml,
+  kHtm,
+  kJar,
+  kPs,
+  kTorrent,
+  kTxt,
+  kZip,
+  kDirectory,
+  kNoExtension,
+  kUnknownExtension,
+  kMhtml,
+  kGdoc,
+  kGsheet,
+  kGslides,
+  kArw,
+  kCr2,
+  kDng,
+  kNef,
+  kNrw,
+  kOrf,
+  kRaf,
+  kRw2,
+  kTini,
+  kMaxValue = kTini,
+};
+
+ViewFileType GetViewFileType(const base::FilePath& path);
+
+}  // namespace file_manager::file_tasks
+
+#endif  // CHROME_BROWSER_ASH_FILE_MANAGER_UMA_ENUMS_GEN_H_
diff --git a/chrome/browser/ash/file_manager/uma_enums_unittest.cc b/chrome/browser/ash/file_manager/uma_enums_unittest.cc
new file mode 100644
index 0000000..4e59e04
--- /dev/null
+++ b/chrome/browser/ash/file_manager/uma_enums_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/file_manager/uma_enums.gen.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace file_manager::file_tasks {
+
+TEST(UmaEnumsTest, Test) {
+  EXPECT_EQ(GetViewFileType(base::FilePath("file.jpg")), ViewFileType::kJpg);
+  EXPECT_EQ(GetViewFileType(base::FilePath("file.jpeg")), ViewFileType::kJpeg);
+  EXPECT_EQ(GetViewFileType(base::FilePath("file.pdf")), ViewFileType::kPdf);
+  EXPECT_EQ(GetViewFileType(base::FilePath("file.tini")), ViewFileType::kTini);
+  EXPECT_EQ(GetViewFileType(base::FilePath("file.xyz")), ViewFileType::kOther);
+  EXPECT_EQ(GetViewFileType(base::FilePath("other")), ViewFileType::kOther);
+}
+
+}  // namespace file_manager::file_tasks
diff --git a/chrome/browser/ash/input_method/grammar_manager.cc b/chrome/browser/ash/input_method/grammar_manager.cc
index 7e56177..4771681 100644
--- a/chrome/browser/ash/input_method/grammar_manager.cc
+++ b/chrome/browser/ash/input_method/grammar_manager.cc
@@ -333,13 +333,17 @@
     // GetSurroundingTextInfo().
     const SurroundingTextInfo surrounding_text =
         input_context->GetSurroundingTextInfo();
+    // Convert selection_range from surrounding_text relative to absolute.
+    const gfx::Range selection_range(
+        surrounding_text.selection_range.start() + surrounding_text.offset,
+        surrounding_text.selection_range.end() + surrounding_text.offset);
 
     // Delete the incorrect grammar fragment.
-    DCHECK(current_fragment_.range.Contains(surrounding_text.selection_range));
-    const uint32_t before = surrounding_text.selection_range.start() -
-                            current_fragment_.range.start();
+    DCHECK(current_fragment_.range.Contains(selection_range));
+    const uint32_t before =
+        selection_range.start() - current_fragment_.range.start();
     const uint32_t after =
-        current_fragment_.range.end() - surrounding_text.selection_range.end();
+        current_fragment_.range.end() - selection_range.end();
     input_context->DeleteSurroundingText(before, after);
     // Insert the suggestion and put cursor after it.
     input_context->CommitText(
diff --git a/chrome/browser/ash/input_method/input_method_engine_browsertests.cc b/chrome/browser/ash/input_method/input_method_engine_browsertests.cc
index a62845e..a72f80cc 100644
--- a/chrome/browser/ash/input_method/input_method_engine_browsertests.cc
+++ b/chrome/browser/ash/input_method/input_method_engine_browsertests.cc
@@ -497,7 +497,7 @@
         "});";
 
     ASSERT_TRUE(
-        content::ExecuteScript(host->host_contents(), commit_text_test_script));
+        content::ExecJs(host->host_contents(), commit_text_test_script));
     EXPECT_EQ(1, mock_input_context->commit_text_call_count());
     EXPECT_EQ(u"COMMIT_TEXT", mock_input_context->last_commit_text());
   }
@@ -516,8 +516,8 @@
         "  }]"
         "});";
 
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       send_key_events_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), send_key_events_test_script));
 
     ASSERT_EQ(1u, mock_input_context->sent_key_events().size());
     const ui::KeyEvent& key_event =
@@ -544,8 +544,8 @@
         "  }]"
         "});";
 
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       send_key_events_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), send_key_events_test_script));
 
     ASSERT_EQ(1u, mock_input_context->sent_key_events().size());
     const ui::KeyEvent& key_event =
@@ -573,8 +573,8 @@
         "  }]"
         "});";
 
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       send_key_events_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), send_key_events_test_script));
 
     ASSERT_EQ(1u, mock_input_context->sent_key_events().size());
     const ui::KeyEvent& key_event =
@@ -606,8 +606,8 @@
         "  }]"
         "});";
 
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_composition_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), set_composition_test_script));
     EXPECT_EQ(1, mock_input_context->update_preedit_text_call_count());
 
     EXPECT_EQ(
@@ -645,8 +645,8 @@
         "  contextID: engineBridge.getFocusedContextID().contextID,"
         "});";
 
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       commite_text_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), commite_text_test_script));
     EXPECT_EQ(1, mock_input_context->update_preedit_text_call_count());
     EXPECT_FALSE(mock_input_context->last_update_composition_arg().is_visible);
     const ui::CompositionText& composition_text =
@@ -666,8 +666,8 @@
         }
       });
     )";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_assistive_window_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_assistive_window_test_script));
     auto* assistive_window_controller = static_cast<AssistiveWindowController*>(
         IMEBridge::Get()->GetAssistiveWindowHandler());
 
@@ -696,8 +696,8 @@
         IMEBridge::Get()->GetAssistiveWindowHandler());
     views::test::WidgetDestroyedWaiter waiter(
         assistive_window_controller->GetUndoWindowForTesting()->GetWidget());
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_assistive_window_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_assistive_window_test_script));
     waiter.Wait();
     ui::ime::UndoWindow* undo_window =
         assistive_window_controller->GetUndoWindowForTesting();
@@ -716,8 +716,8 @@
         }
       });
     )";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_assistive_window_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_assistive_window_test_script));
     auto* assistive_window_controller = static_cast<AssistiveWindowController*>(
         IMEBridge::Get()->GetAssistiveWindowHandler());
 
@@ -758,11 +758,11 @@
         highlighted: true
       });
     )";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_assistive_window_test_script));
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(),
-        set_assistive_window_button_highlighted_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_assistive_window_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(),
+                        set_assistive_window_button_highlighted_test_script));
     auto* assistive_window_controller = static_cast<AssistiveWindowController*>(
         IMEBridge::Get()->GetAssistiveWindowHandler());
 
@@ -796,11 +796,11 @@
         highlighted: false
       });
     )";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_assistive_window_test_script));
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(),
-        set_assistive_window_button_highlighted_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_assistive_window_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(),
+                        set_assistive_window_button_highlighted_test_script));
     auto* assistive_window_controller = static_cast<AssistiveWindowController*>(
         IMEBridge::Get()->GetAssistiveWindowHandler());
 
@@ -824,8 +824,8 @@
         "    visible: true,"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
     EXPECT_TRUE(
         mock_candidate_window->last_update_lookup_table_arg().is_visible);
@@ -842,8 +842,8 @@
         "    cursorVisible: true,"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     // window visibility is kept as before.
@@ -866,8 +866,8 @@
         "    vertical: true,"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     // window visibility is kept as before.
@@ -894,8 +894,8 @@
         "    pageSize: 7,"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     // window visibility is kept as before.
@@ -925,8 +925,8 @@
         "    auxiliaryTextVisible: true"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     const ui::CandidateWindow& table =
@@ -945,8 +945,8 @@
         "    auxiliaryText: 'AUXILIARY_TEXT'"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     // aux text visibility is kept as before.
@@ -967,8 +967,8 @@
         "    currentCandidateIndex: 1"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     const ui::CandidateWindow& table =
@@ -987,8 +987,8 @@
         "    totalCandidates: 100"
         "  }"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(
-        host->host_contents(), set_candidate_window_properties_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_candidate_window_properties_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     const ui::CandidateWindow& table =
@@ -1026,8 +1026,8 @@
         "    }"
         "  }]"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_candidates_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), set_candidates_test_script));
 
     // window visibility is kept as before.
     EXPECT_TRUE(
@@ -1072,8 +1072,8 @@
         "  contextID: engineBridge.getFocusedContextID().contextID,"
         "  candidateID: 2"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_cursor_position_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                set_cursor_position_test_script));
     EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
 
     // window visibility is kept as before.
@@ -1128,8 +1128,8 @@
         "    checked: true"
         "  }]"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_menu_item_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), set_menu_item_test_script));
 
     const ui::ime::InputMethodMenuItemList& props =
         ui::ime::InputMethodMenuManager::GetInstance()
@@ -1161,8 +1161,8 @@
         "  offset: -1,"
         "  length: 3"
         "});";
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       delete_surrounding_text_test_script));
+    ASSERT_TRUE(content::ExecJs(host->host_contents(),
+                                delete_surrounding_text_test_script));
 
     EXPECT_EQ(1, mock_input_context->delete_surrounding_text_call_count());
     EXPECT_EQ(1u, mock_input_context->last_delete_surrounding_text_arg()
@@ -1197,9 +1197,9 @@
         input.focus();
       )";
 
-      ASSERT_TRUE(content::ExecuteScript(
-          browser()->tab_strip_model()->GetActiveWebContents(),
-          password_field_change_to_text_script.data()));
+      ASSERT_TRUE(
+          content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                          password_field_change_to_text_script.data()));
 
       ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
       ASSERT_TRUE(focus_listener.was_satisfied());
@@ -1266,8 +1266,8 @@
         "  }]"
         "});";
 
-    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
-                                       set_composition_test_script));
+    ASSERT_TRUE(
+        content::ExecJs(host->host_contents(), set_composition_test_script));
     EXPECT_EQ(
         2U,
         mock_input_context->last_update_composition_arg().selection.start());
@@ -1493,7 +1493,7 @@
         "  text:'COMMIT_TEXT'"
         "});";
     ASSERT_TRUE(
-        content::ExecuteScript(host->host_contents(), commit_text_test_script));
+        content::ExecJs(host->host_contents(), commit_text_test_script));
     tic.WaitUntilCalled();
     EXPECT_EQ(u"COMMIT_TEXT", tic.inserted_text());
   }
diff --git a/chrome/browser/ash/login/configuration_based_oobe_browsertest.cc b/chrome/browser/ash/login/configuration_based_oobe_browsertest.cc
index 91c9db27b..f37cf2b4 100644
--- a/chrome/browser/ash/login/configuration_based_oobe_browsertest.cc
+++ b/chrome/browser/ash/login/configuration_based_oobe_browsertest.cc
@@ -5,7 +5,6 @@
 #include "ash/constants/ash_switches.h"
 #include "base/test/scoped_chromeos_version_info.h"
 #include "build/build_config.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/enrollment_helper_mixin.h"
 #include "chrome/browser/ash/login/test/enrollment_ui_mixin.h"
 #include "chrome/browser/ash/login/test/hid_controller_mixin.h"
@@ -16,6 +15,7 @@
 #include "chrome/browser/ash/login/test/oobe_screens_utils.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/chrome_notification_types.h"
diff --git a/chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.cc b/chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.cc
index 007b04f6b..abf325d 100644
--- a/chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.cc
+++ b/chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.cc
@@ -12,7 +12,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/ash/device_sync/device_sync_client_factory.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_window.h"
 #include "chromeos/ash/components/multidevice/logging/logging.h"
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
index 2c66c11..4e09a32 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
@@ -22,10 +22,13 @@
 #include "base/version.h"
 #include "build/build_config.h"
 #include "chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.h"
+#include "chrome/browser/ash/login/easy_unlock/easy_unlock_notification_controller.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h"
+#include "chrome/browser/ash/login/easy_unlock/smartlock_feature_usage_metrics.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/pref_names.h"
@@ -36,6 +39,7 @@
 #include "chromeos/ash/components/proximity_auth/proximity_auth_profile_pref_manager.h"
 #include "chromeos/ash/components/proximity_auth/proximity_auth_system.h"
 #include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
+#include "chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h"
 #include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "components/account_id/account_id.h"
@@ -43,6 +47,7 @@
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/session_manager/core/session_manager.h"
 #include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
 #include "components/version_info/version_info.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -51,6 +56,18 @@
 
 namespace {
 
+enum class SmartLockEnabledState {
+  ENABLED = 0,
+  DISABLED = 1,
+  UNSET = 2,
+  COUNT
+};
+
+void LogSmartLockEnabledState(SmartLockEnabledState state) {
+  UMA_HISTOGRAM_ENUMERATION("SmartLock.EnabledState", state,
+                            SmartLockEnabledState::COUNT);
+}
+
 void SetAuthTypeIfChanged(
     proximity_auth::ScreenlockBridge::LockHandler* lock_handler,
     const AccountId& account_id,
@@ -81,6 +98,13 @@
   return EasyUnlockService::Get(profile);
 }
 
+// static
+void EasyUnlockService::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterDictionaryPref(prefs::kEasyUnlockPairing);
+  proximity_auth::ProximityAuthProfilePrefManager::RegisterPrefs(registry);
+}
+
 class EasyUnlockService::PowerMonitor
     : public chromeos::PowerManagerClient::Observer {
  public:
@@ -120,134 +144,33 @@
 
 EasyUnlockService::EasyUnlockService(
     Profile* profile,
-    secure_channel::SecureChannelClient* secure_channel_client)
+    secure_channel::SecureChannelClient* secure_channel_client,
+    device_sync::DeviceSyncClient* device_sync_client,
+    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
+    : EasyUnlockService(
+          profile,
+          secure_channel_client,
+          std::make_unique<EasyUnlockNotificationController>(profile),
+          device_sync_client,
+          multidevice_setup_client) {}
+
+EasyUnlockService::EasyUnlockService(
+    Profile* profile,
+    secure_channel::SecureChannelClient* secure_channel_client,
+    std::unique_ptr<EasyUnlockNotificationController> notification_controller,
+    device_sync::DeviceSyncClient* device_sync_client,
+    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
     : profile_(profile),
       secure_channel_client_(secure_channel_client),
       proximity_auth_client_(profile),
-      shut_down_(false) {}
+      shut_down_(false),
+      lock_screen_last_shown_timestamp_(base::TimeTicks::Now()),
+      notification_controller_(std::move(notification_controller)),
+      device_sync_client_(device_sync_client),
+      multidevice_setup_client_(multidevice_setup_client) {}
 
 EasyUnlockService::~EasyUnlockService() = default;
 
-// static
-void EasyUnlockService::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterDictionaryPref(prefs::kEasyUnlockPairing);
-  proximity_auth::ProximityAuthProfilePrefManager::RegisterPrefs(registry);
-}
-
-void EasyUnlockService::Initialize() {
-  proximity_auth::ScreenlockBridge::Get()->AddObserver(this);
-
-  InitializeInternal();
-}
-
-proximity_auth::ProximityAuthPrefManager*
-EasyUnlockService::GetProximityAuthPrefManager() {
-  NOTREACHED();
-  return nullptr;
-}
-
-bool EasyUnlockService::IsAllowed() const {
-  if (shut_down_)
-    return false;
-
-  if (!IsAllowedInternal())
-    return false;
-
-  return true;
-}
-
-bool EasyUnlockService::IsEnabled() const {
-  return false;
-}
-
-SmartLockState EasyUnlockService::GetInitialSmartLockState() const {
-  if (IsAllowed() && IsEnabled() && proximity_auth_system_ != nullptr)
-    return SmartLockState::kConnectingToPhone;
-
-  return SmartLockState::kDisabled;
-}
-
-SmartLockStateHandler* EasyUnlockService::GetSmartLockStateHandler() {
-  if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp))
-    return nullptr;
-
-  if (!IsAllowed())
-    return nullptr;
-  if (!smartlock_state_handler_) {
-    smartlock_state_handler_ = std::make_unique<SmartLockStateHandler>(
-        GetAccountId(), proximity_auth::ScreenlockBridge::Get());
-  }
-  return smartlock_state_handler_.get();
-}
-
-void EasyUnlockService::UpdateSmartLockState(SmartLockState state) {
-  if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
-    if (smart_lock_state_ && state == smart_lock_state_.value())
-      return;
-
-    smart_lock_state_ = state;
-
-    if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) {
-      auto* lock_handler =
-          proximity_auth::ScreenlockBridge::Get()->lock_handler();
-      DCHECK(lock_handler);
-
-      lock_handler->SetSmartLockState(GetAccountId(), state);
-
-      // TODO(https://crbug.com/1233614): Eventually we would like to remove
-      // auth_type.mojom where AuthType lives, but this will require further
-      // investigation. This logic was copied from
-      // SmartLockStateHandler::UpdateScreenlockAuthType.
-      // Do not override online signin.
-      if (lock_handler->GetAuthType(GetAccountId()) !=
-          proximity_auth::mojom::AuthType::ONLINE_SIGN_IN) {
-        if (smart_lock_state_ == SmartLockState::kPhoneAuthenticated) {
-          SetAuthTypeIfChanged(
-              lock_handler, GetAccountId(),
-              proximity_auth::mojom::AuthType::USER_CLICK,
-              l10n_util::GetStringUTF16(
-                  IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE));
-        } else {
-          SetAuthTypeIfChanged(
-              lock_handler, GetAccountId(),
-              proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
-              std::u16string());
-        }
-      }
-    }
-
-    if (state != SmartLockState::kPhoneAuthenticated && auth_attempt_) {
-      // Clean up existing auth attempt if we can no longer authenticate the
-      // remote device.
-      auth_attempt_.reset();
-
-      if (!IsSmartLockStateValidOnRemoteAuthFailure())
-        HandleAuthFailure(GetAccountId());
-    }
-
-    return;
-  }
-
-  SmartLockStateHandler* handler = GetSmartLockStateHandler();
-  if (!handler)
-    return;
-
-  handler->ChangeState(state);
-
-  if (state != SmartLockState::kPhoneAuthenticated && auth_attempt_) {
-    auth_attempt_.reset();
-
-    if (!handler->InStateValidOnRemoteAuthFailure())
-      HandleAuthFailure(GetAccountId());
-  }
-}
-
-void EasyUnlockService::OnUserEnteredPassword() {
-  if (proximity_auth_system_)
-    proximity_auth_system_->CancelConnectionAttempt();
-}
-
 bool EasyUnlockService::AttemptAuth(const AccountId& account_id) {
   PA_LOG(VERBOSE) << "User began unlock auth attempt.";
 
@@ -296,8 +219,9 @@
 }
 
 void EasyUnlockService::FinalizeUnlock(bool success) {
-  if (!auth_attempt_)
+  if (!auth_attempt_) {
     return;
+  }
 
   set_will_authenticate_using_easy_unlock(true);
   auth_attempt_->FinalizeUnlock(GetAccountId(), success);
@@ -319,25 +243,166 @@
   }
 }
 
+AccountId EasyUnlockService::GetAccountId() const {
+  const user_manager::User* const primary_user =
+      user_manager::UserManager::Get()->GetPrimaryUser();
+  CHECK(primary_user);
+  return primary_user->GetAccountId();
+}
+
+SmartLockState EasyUnlockService::GetInitialSmartLockState() const {
+  if (IsAllowed() && IsEnabled() && proximity_auth_system_ != nullptr) {
+    return SmartLockState::kConnectingToPhone;
+  }
+
+  return SmartLockState::kDisabled;
+}
+
+std::string EasyUnlockService::GetLastRemoteStatusUnlockForLogging() {
+  if (proximity_auth_system_) {
+    return proximity_auth_system_->GetLastRemoteStatusUnlockForLogging();
+  }
+  return std::string();
+}
+
+proximity_auth::ProximityAuthPrefManager*
+EasyUnlockService::GetProximityAuthPrefManager() {
+  return pref_manager_.get();
+}
+
+const multidevice::RemoteDeviceRefList
+EasyUnlockService::GetRemoteDevicesForTesting() const {
+  if (!proximity_auth_system_) {
+    return multidevice::RemoteDeviceRefList();
+  }
+
+  return proximity_auth_system_->GetRemoteDevicesForUser(GetAccountId());
+}
+
 void EasyUnlockService::HandleAuthFailure(const AccountId& account_id) {
-  if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
-    NotifySmartLockAuthResult(/*success=*/false);
+  NotifySmartLockAuthResult(/*success=*/false);
+}
+
+void EasyUnlockService::Initialize() {
+  proximity_auth::ScreenlockBridge::Get()->AddObserver(this);
+
+  pref_manager_ =
+      std::make_unique<proximity_auth::ProximityAuthProfilePrefManager>(
+          profile()->GetPrefs(), multidevice_setup_client_);
+
+  // If `device_sync_client_` is not ready yet, wait for it to call back on
+  // OnReady().
+  if (device_sync_client_->is_ready()) {
+    OnReady();
+  }
+  device_sync_client_->AddObserver(this);
+
+  OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates());
+  multidevice_setup_client_->AddObserver(this);
+  StartFeatureUsageMetrics();
+
+  LoadRemoteDevices();
+}
+
+bool EasyUnlockService::IsAllowed() const {
+  if (shut_down_) {
+    return false;
+  }
+
+  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+
+  if (!user_manager->IsLoggedInAsUserWithGaiaAccount()) {
+    return false;
+  }
+
+  // TODO(tengs): Ephemeral accounts generate a new enrollment every time they
+  // are added, so disable Smart Lock to reduce enrollments on server. However,
+  // ephemeral accounts can be locked, so we should revisit this use case.
+  if (user_manager->IsCurrentUserNonCryptohomeDataEphemeral()) {
+    return false;
+  }
+
+  if (!ProfileHelper::IsPrimaryProfile(profile())) {
+    return false;
+  }
+
+  if (multidevice_setup_client_->GetFeatureState(
+          multidevice_setup::mojom::Feature::kSmartLock) ==
+      multidevice_setup::mojom::FeatureState::kProhibitedByPolicy) {
+    return false;
+  }
+
+  return true;
+}
+
+bool EasyUnlockService::IsEnabled() const {
+  return multidevice_setup_client_->GetFeatureState(
+             multidevice_setup::mojom::Feature::kSmartLock) ==
+         multidevice_setup::mojom::FeatureState::kEnabledByUser;
+}
+
+void EasyUnlockService::UpdateSmartLockState(SmartLockState state) {
+  if (smart_lock_state_ && state == smart_lock_state_.value()) {
     return;
   }
 
-  if (account_id != GetAccountId())
-    return;
+  smart_lock_state_ = state;
 
-  if (!smartlock_state_handler_.get())
-    return;
+  if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) {
+    auto* lock_handler =
+        proximity_auth::ScreenlockBridge::Get()->lock_handler();
+    DCHECK(lock_handler);
+
+    lock_handler->SetSmartLockState(GetAccountId(), state);
+
+    // TODO(https://crbug.com/1233614): Eventually we would like to remove
+    // auth_type.mojom where AuthType lives, but this will require further
+    // investigation. This logic was copied from legacy
+    // SmartLockStateHandler::UpdateScreenlockAuthType.
+    // Do not override online signin.
+    if (lock_handler->GetAuthType(GetAccountId()) !=
+        proximity_auth::mojom::AuthType::ONLINE_SIGN_IN) {
+      if (smart_lock_state_ == SmartLockState::kPhoneAuthenticated) {
+        SetAuthTypeIfChanged(
+            lock_handler, GetAccountId(),
+            proximity_auth::mojom::AuthType::USER_CLICK,
+            l10n_util::GetStringUTF16(
+                IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE));
+      } else {
+        SetAuthTypeIfChanged(lock_handler, GetAccountId(),
+                             proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
+                             std::u16string());
+      }
+    }
+  }
+
+  if (state != SmartLockState::kPhoneAuthenticated && auth_attempt_) {
+    // Clean up existing auth attempt if we can no longer authenticate the
+    // remote device.
+    auth_attempt_.reset();
+
+    if (!IsSmartLockStateValidOnRemoteAuthFailure()) {
+      HandleAuthFailure(GetAccountId());
+    }
+  }
 }
 
 void EasyUnlockService::Shutdown() {
-  if (shut_down_)
+  if (shut_down_) {
     return;
+  }
   shut_down_ = true;
 
-  ShutdownInternal();
+  pref_manager_.reset();
+  notification_controller_.reset();
+
+  device_sync_client_->RemoveObserver(this);
+
+  multidevice_setup_client_->RemoveObserver(this);
+
+  StopFeatureUsageMetrics();
+
+  weak_ptr_factory_.InvalidateWeakPtrs();
 
   proximity_auth::ScreenlockBridge::Get()->RemoveObserver(this);
 
@@ -353,102 +418,204 @@
   if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
     ShowInitialSmartLockState();
   }
+
+  set_will_authenticate_using_easy_unlock(false);
+  lock_screen_last_shown_timestamp_ = base::TimeTicks::Now();
 }
 
-void EasyUnlockService::UpdateAppState() {
-  if (IsAllowed()) {
-    if (proximity_auth_system_)
-      proximity_auth_system_->Start();
-
-    if (!power_monitor_)
-      power_monitor_ = std::make_unique<PowerMonitor>(this);
-  }
-}
-
-void EasyUnlockService::ShowInitialSmartLockState() {
-  // Only proceed if the screen is locked to prevent the UI event from not
-  // persisting within UpdateSmartLockState().
+void EasyUnlockService::OnScreenDidUnlock(
+    proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
+  // If we tried to load remote devices (e.g. after a sync or the
+  // service was initialized) while the screen was locked, we can now
+  // load the new remote devices.
   //
-  // Note: ScreenlockBridge::IsLocked() may return a false positive if the
-  // system is "warming up" (for example, ScreenlockBridge::IsLocked() will
-  // return false when EasyUnlockServiceSignin is first instantiated because of
-  // initialization timing in UserSelectionScreen). To work around this race,
-  // ShowInitialSmartLockState() is also called from OnScreenDidLock() (which
-  // triggers when ScreenlockBridge::IsLocked() becomes true) to ensure that
-  // an initial state is displayed in the UI.
-  auto* screenlock_bridge = proximity_auth::ScreenlockBridge::Get();
-  if (screenlock_bridge && screenlock_bridge->IsLocked()) {
-    UpdateSmartLockState(GetInitialSmartLockState());
+  // It's important to go through this code path even if unlocking the
+  // login screen. Because when the service is initialized while the
+  // user is signing in we need to load the remotes. Otherwise, the
+  // first time the user locks the screen the feature won't work.
+  if (deferring_device_load_) {
+    PA_LOG(VERBOSE) << "Loading deferred devices after screen unlock.";
+    deferring_device_load_ = false;
+    LoadRemoteDevices();
   }
+
+  // Do not process events for the login screen.
+  if (screen_type !=
+      proximity_auth::ScreenlockBridge::LockHandler::LOCK_SCREEN) {
+    return;
+  }
+
+  if (shown_pairing_changed_notification_) {
+    shown_pairing_changed_notification_ = false;
+
+    if (!GetUnlockKeys().empty()) {
+      notification_controller_->ShowPairingChangeAppliedNotification(
+          GetUnlockKeys()[0].name());
+    }
+  }
+
+  // Only record metrics for users who have enabled the feature.
+  if (IsEnabled()) {
+    EasyUnlockAuthEvent event = will_authenticate_using_easy_unlock()
+                                    ? EASY_UNLOCK_SUCCESS
+                                    : GetPasswordAuthEvent();
+    RecordEasyUnlockScreenUnlockEvent(event);
+
+    if (will_authenticate_using_easy_unlock()) {
+      // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric.
+      SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
+          SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kSmartLock);
+      SmartLockMetricsRecorder::RecordAuthResultUnlockSuccess();
+      RecordEasyUnlockScreenUnlockDuration(base::TimeTicks::Now() -
+                                           lock_screen_last_shown_timestamp_);
+    } else {
+      SmartLockMetricsRecorder::RecordAuthMethodChoiceUnlockPasswordState(
+          GetSmartUnlockPasswordAuthEvent());
+      // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric.
+      SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
+          SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kOther);
+      OnUserEnteredPassword();
+    }
+  }
+
+  set_will_authenticate_using_easy_unlock(false);
 }
 
-void EasyUnlockService::ResetSmartLockState() {
-  if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
-    smart_lock_state_.reset();
-  } else {
-    smartlock_state_handler_.reset();
+void EasyUnlockService::OnFocusedUserChanged(const AccountId& account_id) {
+  // Nothing to do.
+}
+
+void EasyUnlockService::OnReady() {
+  // If the local device and synced devices are ready for the first time,
+  // establish what the unlock keys were before the next sync. This is necessary
+  // in order for OnNewDevicesSynced() to determine if new devices were added
+  // since the last sync.
+  remote_device_unlock_keys_before_sync_ = GetUnlockKeys();
+}
+
+void EasyUnlockService::OnEnrollmentFinished() {
+  // The local device may be ready for the first time, or it may have been
+  // updated, so reload devices.
+  LoadRemoteDevices();
+}
+
+void EasyUnlockService::OnNewDevicesSynced() {
+  std::set<std::string> public_keys_before_sync;
+  for (const auto& remote_device : remote_device_unlock_keys_before_sync_) {
+    public_keys_before_sync.insert(remote_device.public_key());
   }
 
-  auth_attempt_.reset();
+  multidevice::RemoteDeviceRefList remote_device_unlock_keys_after_sync =
+      GetUnlockKeys();
+  std::set<std::string> public_keys_after_sync;
+  for (const auto& remote_device : remote_device_unlock_keys_after_sync) {
+    public_keys_after_sync.insert(remote_device.public_key());
+  }
+
+  ShowNotificationIfNewDevicePresent(public_keys_before_sync,
+                                     public_keys_after_sync);
+
+  LoadRemoteDevices();
+
+  remote_device_unlock_keys_before_sync_ = remote_device_unlock_keys_after_sync;
+}
+
+void EasyUnlockService::OnFeatureStatesChanged(
+    const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
+        feature_states_map) {
+  LoadRemoteDevices();
+  UpdateAppState();
+}
+
+EasyUnlockAuthEvent EasyUnlockService::GetPasswordAuthEvent() const {
+  DCHECK(IsEnabled());
+
+  if (!smart_lock_state_) {
+    return PASSWORD_ENTRY_NO_SMARTLOCK_STATE_HANDLER;
+  }
+
+  SmartLockState state = smart_lock_state_.value();
+
+  switch (state) {
+    case SmartLockState::kInactive:
+    case SmartLockState::kDisabled:
+      return PASSWORD_ENTRY_SERVICE_NOT_ACTIVE;
+    case SmartLockState::kBluetoothDisabled:
+      return PASSWORD_ENTRY_NO_BLUETOOTH;
+    case SmartLockState::kConnectingToPhone:
+      return PASSWORD_ENTRY_BLUETOOTH_CONNECTING;
+    case SmartLockState::kPhoneNotFound:
+      return PASSWORD_ENTRY_NO_PHONE;
+    case SmartLockState::kPhoneNotAuthenticated:
+      return PASSWORD_ENTRY_PHONE_NOT_AUTHENTICATED;
+    case SmartLockState::kPhoneFoundLockedAndProximate:
+      return PASSWORD_ENTRY_PHONE_LOCKED;
+    case SmartLockState::kPhoneNotLockable:
+      return PASSWORD_ENTRY_PHONE_NOT_LOCKABLE;
+    case SmartLockState::kPhoneFoundUnlockedAndDistant:
+      return PASSWORD_ENTRY_RSSI_TOO_LOW;
+    case SmartLockState::kPhoneFoundLockedAndDistant:
+      return PASSWORD_ENTRY_PHONE_LOCKED_AND_RSSI_TOO_LOW;
+    case SmartLockState::kPhoneAuthenticated:
+      return PASSWORD_ENTRY_WITH_AUTHENTICATED_PHONE;
+    case SmartLockState::kPrimaryUserAbsent:
+      return PASSWORD_ENTRY_PRIMARY_USER_ABSENT;
+  }
+
+  NOTREACHED();
+  return EASY_UNLOCK_AUTH_EVENT_COUNT;
 }
 
 SmartLockMetricsRecorder::SmartLockAuthEventPasswordState
 EasyUnlockService::GetSmartUnlockPasswordAuthEvent() const {
   DCHECK(IsEnabled());
 
-  if (!base::FeatureList::IsEnabled(features::kSmartLockUIRevamp) &&
-      !smartlock_state_handler()) {
+  if (!smart_lock_state_) {
     return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
         kUnknownState;
-  } else if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp) &&
-             !smart_lock_state_) {
-    return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-        kUnknownState;
-  } else {
-    SmartLockState state =
-        (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp))
-            ? smart_lock_state_.value()
-            : smartlock_state_handler()->state();
-    switch (state) {
-      case SmartLockState::kInactive:
-      case SmartLockState::kDisabled:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kServiceNotActive;
-      case SmartLockState::kBluetoothDisabled:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kNoBluetooth;
-      case SmartLockState::kConnectingToPhone:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kBluetoothConnecting;
-      case SmartLockState::kPhoneNotFound:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kCouldNotConnectToPhone;
-      case SmartLockState::kPhoneNotAuthenticated:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kNotAuthenticated;
-      case SmartLockState::kPhoneFoundLockedAndProximate:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kPhoneLocked;
-      case SmartLockState::kPhoneFoundUnlockedAndDistant:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kRssiTooLow;
-      case SmartLockState::kPhoneFoundLockedAndDistant:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kPhoneLockedAndRssiTooLow;
-      case SmartLockState::kPhoneAuthenticated:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kAuthenticatedPhone;
-      case SmartLockState::kPhoneNotLockable:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kPhoneNotLockable;
-      case SmartLockState::kPrimaryUserAbsent:
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kPrimaryUserAbsent;
-      default:
-        NOTREACHED();
-        return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
-            kUnknownState;
-    }
+  }
+
+  SmartLockState state = smart_lock_state_.value();
+
+  switch (state) {
+    case SmartLockState::kInactive:
+    case SmartLockState::kDisabled:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kServiceNotActive;
+    case SmartLockState::kBluetoothDisabled:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kNoBluetooth;
+    case SmartLockState::kConnectingToPhone:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kBluetoothConnecting;
+    case SmartLockState::kPhoneNotFound:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kCouldNotConnectToPhone;
+    case SmartLockState::kPhoneNotAuthenticated:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kNotAuthenticated;
+    case SmartLockState::kPhoneFoundLockedAndProximate:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kPhoneLocked;
+    case SmartLockState::kPhoneFoundUnlockedAndDistant:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kRssiTooLow;
+    case SmartLockState::kPhoneFoundLockedAndDistant:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kPhoneLockedAndRssiTooLow;
+    case SmartLockState::kPhoneAuthenticated:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kAuthenticatedPhone;
+    case SmartLockState::kPhoneNotLockable:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kPhoneNotLockable;
+    case SmartLockState::kPrimaryUserAbsent:
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kPrimaryUserAbsent;
+    default:
+      NOTREACHED();
+      return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
+          kUnknownState;
   }
 
   NOTREACHED();
@@ -456,50 +623,124 @@
       kUnknownState;
 }
 
-EasyUnlockAuthEvent EasyUnlockService::GetPasswordAuthEvent() const {
-  DCHECK(IsEnabled());
-
-  if (!base::FeatureList::IsEnabled(features::kSmartLockUIRevamp) &&
-      !smartlock_state_handler()) {
-    return PASSWORD_ENTRY_NO_SMARTLOCK_STATE_HANDLER;
-  } else if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp) &&
-             !smart_lock_state_) {
-    return PASSWORD_ENTRY_NO_SMARTLOCK_STATE_HANDLER;
-  } else {
-    SmartLockState state =
-        (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp))
-            ? smart_lock_state_.value()
-            : smartlock_state_handler()->state();
-
-    switch (state) {
-      case SmartLockState::kInactive:
-      case SmartLockState::kDisabled:
-        return PASSWORD_ENTRY_SERVICE_NOT_ACTIVE;
-      case SmartLockState::kBluetoothDisabled:
-        return PASSWORD_ENTRY_NO_BLUETOOTH;
-      case SmartLockState::kConnectingToPhone:
-        return PASSWORD_ENTRY_BLUETOOTH_CONNECTING;
-      case SmartLockState::kPhoneNotFound:
-        return PASSWORD_ENTRY_NO_PHONE;
-      case SmartLockState::kPhoneNotAuthenticated:
-        return PASSWORD_ENTRY_PHONE_NOT_AUTHENTICATED;
-      case SmartLockState::kPhoneFoundLockedAndProximate:
-        return PASSWORD_ENTRY_PHONE_LOCKED;
-      case SmartLockState::kPhoneNotLockable:
-        return PASSWORD_ENTRY_PHONE_NOT_LOCKABLE;
-      case SmartLockState::kPhoneFoundUnlockedAndDistant:
-        return PASSWORD_ENTRY_RSSI_TOO_LOW;
-      case SmartLockState::kPhoneFoundLockedAndDistant:
-        return PASSWORD_ENTRY_PHONE_LOCKED_AND_RSSI_TOO_LOW;
-      case SmartLockState::kPhoneAuthenticated:
-        return PASSWORD_ENTRY_WITH_AUTHENTICATED_PHONE;
-      case SmartLockState::kPrimaryUserAbsent:
-        return PASSWORD_ENTRY_PRIMARY_USER_ABSENT;
+multidevice::RemoteDeviceRefList EasyUnlockService::GetUnlockKeys() {
+  multidevice::RemoteDeviceRefList unlock_keys;
+  for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
+    bool unlock_key = remote_device.GetSoftwareFeatureState(
+                          multidevice::SoftwareFeature::kSmartLockHost) ==
+                      multidevice::SoftwareFeatureState::kEnabled;
+    if (unlock_key) {
+      unlock_keys.push_back(remote_device);
     }
   }
+  return unlock_keys;
+}
 
-  NOTREACHED();
-  return EASY_UNLOCK_AUTH_EVENT_COUNT;
+bool EasyUnlockService::IsSmartLockStateValidOnRemoteAuthFailure() const {
+  // Note that NO_PHONE is not valid in this case because the phone may close
+  // the connection if the auth challenge sent to it is invalid. This case
+  // should be handled as authentication failure.
+  return smart_lock_state_ == SmartLockState::kInactive ||
+         smart_lock_state_ == SmartLockState::kDisabled ||
+         smart_lock_state_ == SmartLockState::kBluetoothDisabled ||
+         smart_lock_state_ == SmartLockState::kPhoneFoundLockedAndProximate;
+}
+
+// TODO(jhawkins): This method with `has_unlock_keys` == true is the only signal
+// that SmartLock setup has completed successfully. Make this signal more
+// explicit.
+void EasyUnlockService::LoadRemoteDevices() {
+  if (!device_sync_client_->is_ready()) {
+    // OnEnrollmentFinished() or OnNewDevicesSynced() will call back on this
+    // method once `device_sync_client_` is ready.
+    PA_LOG(VERBOSE) << "DeviceSyncClient is not ready yet, delaying "
+                       "UseLoadedRemoteDevices().";
+    return;
+  }
+
+  if (!IsEnabled()) {
+    // OnFeatureStatesChanged() will call back on this method when feature state
+    // changes.
+    PA_LOG(VERBOSE) << "Smart Lock is not enabled by user; aborting.";
+    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
+                            absl::nullopt /* local_device */);
+    return;
+  }
+
+  bool has_unlock_keys = !GetUnlockKeys().empty();
+
+  // TODO(jhawkins): The enabled pref should not be tied to whether unlock keys
+  // exist; instead, both of these variables should be used to determine
+  // IsEnabled().
+  pref_manager_->SetIsEasyUnlockEnabled(has_unlock_keys);
+  if (has_unlock_keys) {
+    // If `has_unlock_keys` is true, then the user must have successfully
+    // completed setup. Track that the IsEasyUnlockEnabled pref is actively set
+    // by the user, as opposed to passively being set to disabled (the default
+    // state).
+    pref_manager_->SetEasyUnlockEnabledStateSet();
+    LogSmartLockEnabledState(SmartLockEnabledState::ENABLED);
+  } else {
+    PA_LOG(ERROR) << "Smart Lock is enabled by user, but no unlock key is "
+                     "present; aborting.";
+    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
+                            absl::nullopt /* local_device */);
+
+    if (pref_manager_->IsEasyUnlockEnabledStateSet()) {
+      LogSmartLockEnabledState(SmartLockEnabledState::DISABLED);
+    } else {
+      LogSmartLockEnabledState(SmartLockEnabledState::UNSET);
+    }
+    return;
+  }
+
+  // This code path may be hit by:
+  //   1. New devices were synced on the lock screen.
+  //   2. The service was initialized while the login screen is still up.
+  if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) {
+    PA_LOG(VERBOSE) << "Deferring device load until screen is unlocked.";
+    deferring_device_load_ = true;
+    return;
+  }
+
+  UseLoadedRemoteDevices(GetUnlockKeys());
+}
+
+void EasyUnlockService::NotifySmartLockAuthResult(bool success) {
+  if (!proximity_auth::ScreenlockBridge::Get()->IsLocked()) {
+    return;
+  }
+
+  proximity_auth::ScreenlockBridge::Get()
+      ->lock_handler()
+      ->NotifySmartLockAuthResult(GetAccountId(), success);
+}
+
+void EasyUnlockService::OnSuspendDone() {
+  if (proximity_auth_system_) {
+    proximity_auth_system_->OnSuspendDone();
+  }
+}
+
+void EasyUnlockService::OnUserEnteredPassword() {
+  if (proximity_auth_system_) {
+    proximity_auth_system_->CancelConnectionAttempt();
+  }
+}
+
+void EasyUnlockService::PrepareForSuspend() {
+  if (smart_lock_state_ && *smart_lock_state_ != SmartLockState::kInactive) {
+    ShowInitialSmartLockState();
+  }
+
+  if (proximity_auth_system_) {
+    proximity_auth_system_->OnSuspend();
+  }
+}
+
+void EasyUnlockService::ResetSmartLockState() {
+  smart_lock_state_.reset();
+  auth_attempt_.reset();
 }
 
 void EasyUnlockService::SetProximityAuthDevices(
@@ -526,59 +767,112 @@
   proximity_auth_system_->Start();
 }
 
-void EasyUnlockService::PrepareForSuspend() {
-  if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
-    if (smart_lock_state_ && *smart_lock_state_ != SmartLockState::kInactive) {
-      ShowInitialSmartLockState();
-    }
-  } else {
-    if (smartlock_state_handler_ && smartlock_state_handler_->IsActive()) {
-      UpdateSmartLockState(SmartLockState::kConnectingToPhone);
-    }
+void EasyUnlockService::ShowChromebookAddedNotification() {
+  // The user may have decided to disable Smart Lock or the whole multidevice
+  // suite immediately after completing setup, so ensure that Smart Lock is
+  // enabled.
+  if (IsEnabled()) {
+    notification_controller_->ShowChromebookAddedNotification();
   }
-
-  if (proximity_auth_system_)
-    proximity_auth_system_->OnSuspend();
 }
 
-void EasyUnlockService::OnSuspendDone() {
-  if (proximity_auth_system_)
-    proximity_auth_system_->OnSuspendDone();
+void EasyUnlockService::ShowInitialSmartLockState() {
+  // Only proceed if the screen is locked to prevent the UI event from not
+  // persisting within UpdateSmartLockState().
+  //
+  // Note: ScreenlockBridge::IsLocked() may return a false positive if the
+  // system is "warming up". To work around this race,
+  // ShowInitialSmartLockState() is also called from OnScreenDidLock() (which
+  // triggers when ScreenlockBridge::IsLocked() becomes true) to ensure that
+  // an initial state is displayed in the UI.
+  // TODO(b/227674947) Investigate whether a false positive is still possible
+  // now that Sign in with Smart Lock is deprecated.
+  auto* screenlock_bridge = proximity_auth::ScreenlockBridge::Get();
+  if (screenlock_bridge && screenlock_bridge->IsLocked()) {
+    UpdateSmartLockState(GetInitialSmartLockState());
+  }
 }
 
-bool EasyUnlockService::IsSmartLockStateValidOnRemoteAuthFailure() const {
-  // Note that NO_PHONE is not valid in this case because the phone may close
-  // the connection if the auth challenge sent to it is invalid. This case
-  // should be handled as authentication failure.
-  return smart_lock_state_ == SmartLockState::kInactive ||
-         smart_lock_state_ == SmartLockState::kDisabled ||
-         smart_lock_state_ == SmartLockState::kBluetoothDisabled ||
-         smart_lock_state_ == SmartLockState::kPhoneFoundLockedAndProximate;
-}
-
-void EasyUnlockService::NotifySmartLockAuthResult(bool success) {
-  if (!proximity_auth::ScreenlockBridge::Get()->IsLocked())
+void EasyUnlockService::ShowNotificationIfNewDevicePresent(
+    const std::set<std::string>& public_keys_before_sync,
+    const std::set<std::string>& public_keys_after_sync) {
+  if (public_keys_before_sync == public_keys_after_sync) {
     return;
-
-  proximity_auth::ScreenlockBridge::Get()
-      ->lock_handler()
-      ->NotifySmartLockAuthResult(GetAccountId(), success);
-}
-
-std::string EasyUnlockService::GetLastRemoteStatusUnlockForLogging() {
-  if (proximity_auth_system_) {
-    return proximity_auth_system_->GetLastRemoteStatusUnlockForLogging();
-  }
-  return std::string();
-}
-
-const multidevice::RemoteDeviceRefList
-EasyUnlockService::GetRemoteDevicesForTesting() const {
-  if (!proximity_auth_system_) {
-    return multidevice::RemoteDeviceRefList();
   }
 
-  return proximity_auth_system_->GetRemoteDevicesForUser(GetAccountId());
+  // Show the appropriate notification if an unlock key is first synced or if it
+  // changes an existing key.
+  // Note: We do not show a notification when EasyUnlock is disabled by sync nor
+  // if EasyUnlock was enabled through the setup app.
+  if (!public_keys_after_sync.empty()) {
+    if (public_keys_before_sync.empty()) {
+      multidevice_setup::MultiDeviceSetupDialog* multidevice_setup_dialog =
+          multidevice_setup::MultiDeviceSetupDialog::Get();
+      if (multidevice_setup_dialog) {
+        // Delay showing the "Chromebook added" notification until the
+        // MultiDeviceSetupDialog is closed.
+        multidevice_setup_dialog->AddOnCloseCallback(
+            base::BindOnce(&EasyUnlockService::ShowChromebookAddedNotification,
+                           weak_ptr_factory_.GetWeakPtr()));
+        return;
+      }
+
+      notification_controller_->ShowChromebookAddedNotification();
+    } else {
+      shown_pairing_changed_notification_ = true;
+      notification_controller_->ShowPairingChangeNotification();
+    }
+  }
+}
+
+void EasyUnlockService::StartFeatureUsageMetrics() {
+  feature_usage_metrics_ =
+      std::make_unique<SmartLockFeatureUsageMetrics>(multidevice_setup_client_);
+
+  SmartLockMetricsRecorder::SetUsageRecorderInstance(
+      feature_usage_metrics_.get());
+}
+
+void EasyUnlockService::StopFeatureUsageMetrics() {
+  feature_usage_metrics_.reset();
+  SmartLockMetricsRecorder::SetUsageRecorderInstance(nullptr);
+}
+
+void EasyUnlockService::UpdateAppState() {
+  if (IsAllowed()) {
+    if (proximity_auth_system_) {
+      proximity_auth_system_->Start();
+    }
+
+    if (!power_monitor_) {
+      power_monitor_ = std::make_unique<PowerMonitor>(this);
+    }
+  }
+}
+
+void EasyUnlockService::UseLoadedRemoteDevices(
+    const multidevice::RemoteDeviceRefList& remote_devices) {
+  // When EasyUnlock is enabled, only one EasyUnlock host should exist.
+  if (remote_devices.size() != 1u) {
+    PA_LOG(ERROR) << "There should only be 1 Smart Lock host, but there are: "
+                  << remote_devices.size();
+    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
+                            absl::nullopt);
+    NOTREACHED();
+    return;
+  }
+
+  absl::optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "EasyUnlockService::" << __func__
+                  << ": Local device unexpectedly null.";
+    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
+                            absl::nullopt);
+    return;
+  }
+
+  SetProximityAuthDevices(GetAccountId(), remote_devices, local_device);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h
index 21d93b5a..2e98482 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h
@@ -12,42 +12,50 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_auth_attempt.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_metrics.h"
-#include "chrome/browser/ash/login/easy_unlock/smartlock_state_handler.h"
 #include "chromeos/ash/components/multidevice/remote_device_ref.h"
+#include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
 #include "chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h"
+#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
+#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 class AccountId;
+class Profile;
 
 namespace user_manager {
 class User;
-}
+}  // namespace user_manager
 
 namespace user_prefs {
 class PrefRegistrySyncable;
-}
+}  // namespace user_prefs
 
 namespace proximity_auth {
 class ProximityAuthPrefManager;
+class ProximityAuthProfilePrefManager;
 class ProximityAuthSystem;
 }  // namespace proximity_auth
 
-class Profile;
-
 namespace ash {
 
+class EasyUnlockNotificationController;
+class SmartLockFeatureUsageMetrics;
 enum class SmartLockState;
 
 namespace secure_channel {
 class SecureChannelClient;
-}
+}  // namespace secure_channel
 
-class EasyUnlockService : public KeyedService,
-                          public proximity_auth::ScreenlockBridge::Observer {
+class EasyUnlockService
+    : public KeyedService,
+      public proximity_auth::ScreenlockBridge::Observer,
+      public device_sync::DeviceSyncClient::Observer,
+      public multidevice_setup::MultiDeviceSetupClient::Observer {
  public:
   // Gets EasyUnlockService instance.
   static EasyUnlockService* Get(Profile* profile);
@@ -56,19 +64,66 @@
   // logged in and their profile is initialized.
   static EasyUnlockService* GetForUser(const user_manager::User& user);
 
-  EasyUnlockService(const EasyUnlockService&) = delete;
-  EasyUnlockService& operator=(const EasyUnlockService&) = delete;
-
   // Registers Easy Unlock profile preferences.
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  // Returns the ProximityAuthPrefManager, responsible for managing all
-  // EasyUnlock preferences.
-  virtual proximity_auth::ProximityAuthPrefManager*
-  GetProximityAuthPrefManager();
+  EasyUnlockService(
+      Profile* profile,
+      secure_channel::SecureChannelClient* secure_channel_client,
+      device_sync::DeviceSyncClient* device_sync_client,
+      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
+
+  // Constructor for tests.
+  EasyUnlockService(
+      Profile* profile,
+      secure_channel::SecureChannelClient* secure_channel_client,
+      std::unique_ptr<EasyUnlockNotificationController> notification_controller,
+      device_sync::DeviceSyncClient* device_sync_client,
+      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
+
+  EasyUnlockService(const EasyUnlockService&) = delete;
+  EasyUnlockService& operator=(const EasyUnlockService&) = delete;
+  ~EasyUnlockService() override;
+
+  // Starts an auth attempt for the user associated with the service. Returns
+  // true if no other attempt is in progress and the attempt request can be
+  // processed.
+  bool AttemptAuth(const AccountId& account_id);
+
+  // Finalizes the previously started auth attempt for easy unlock.
+  void FinalizeUnlock(bool success);
 
   // Returns the user currently associated with the service.
-  virtual AccountId GetAccountId() const = 0;
+  AccountId GetAccountId() const;
+
+  // To be called when EasyUnlockService is "warming up", for example, on screen
+  // lock, after suspend, when the login screen is starting up, etc. During a
+  // period like this, not all sub-systems are fully initialized, particularly
+  // UnlockManager and the Bluetooth stack, so to avoid UI jank, callers can use
+  // this method to fill in the UI with an approximation of what the UI will
+  // look like in <1 second. The resulting initial state will be one of two
+  // possibilities:
+  //   * SmartLockState::kConnectingToPhone: if the feature is allowed, enabled,
+  //     and has kicked off a scan/connection.
+  //   * SmartLockState::kDisabled: if any values above can't be confirmed.
+  SmartLockState GetInitialSmartLockState() const;
+
+  // The last value emitted to the SmartLock.GetRemoteStatus.Unlock(.Failure)
+  // metrics. Helps to understand whether/why not Smart Lock was an available
+  // choice for unlock. Returns the empty string if the ProximityAuthSystem or
+  // the UnlockManager is uninitialized.
+  std::string GetLastRemoteStatusUnlockForLogging();
+
+  // Returns the ProximityAuthPrefManager, responsible for managing all
+  // EasyUnlock preferences.
+  proximity_auth::ProximityAuthPrefManager* GetProximityAuthPrefManager();
+
+  // Retrieves the remote device list stored for the account in
+  // |proximity_auth_system_|.
+  const multidevice::RemoteDeviceRefList GetRemoteDevicesForTesting() const;
+
+  // Handles Easy Unlock auth failure for the user.
+  void HandleAuthFailure(const AccountId& account_id);
 
   // Sets the service up and schedules service initialization.
   void Initialize();
@@ -82,72 +137,16 @@
   // override for testing.
   virtual bool IsEnabled() const;
 
-  // To be called when EasyUnlockService is "warming up", for example, on screen
-  // lock, after suspend, when the login screen is starting up, etc. During a
-  // period like this, not all sub-systems are fully initialized, particularly
-  // UnlockManager and the Bluetooth stack, so to avoid UI jank, callers can use
-  // this method to fill in the UI with an approximation of what the UI will
-  // look like in <1 second. The resulting initial state will be one of two
-  // possibilities:
-  //   * SmartLockState::kConnectingToPhone: if the feature is allowed, enabled,
-  //     and has kicked off a scan/connection.
-  //   * SmartLockState::kDisabled: if any values above can't be confirmed.
-  virtual SmartLockState GetInitialSmartLockState() const;
-
-  // Updates the user pod on the signin/lock screen for the user associated with
-  // the service to reflect the provided Smart Lock state.
-  void UpdateSmartLockState(SmartLockState state);
-
-  // Starts an auth attempt for the user associated with the service. The
-  // attempt type (unlock vs. signin) will depend on the service type. Returns
-  // true if no other attempt is in progress and the attempt request can be
-  // processed.
-  bool AttemptAuth(const AccountId& account_id);
-
-  // Finalizes the previously started auth attempt for easy unlock. If called on
-  // signin profile service, it will cancel the current auth attempt if one
-  // exists.
-  void FinalizeUnlock(bool success);
-
-  // Handles Easy Unlock auth failure for the user.
-  void HandleAuthFailure(const AccountId& account_id);
-
   ChromeProximityAuthClient* proximity_auth_client() {
     return &proximity_auth_client_;
   }
 
-  // The last value emitted to the SmartLock.GetRemoteStatus.Unlock(.Failure)
-  // metrics. Helps to understand whether/why not Smart Lock was an available
-  // choice for unlock. Returns the empty string if the ProximityAuthSystem or
-  // the UnlockManager is uninitialized.
-  std::string GetLastRemoteStatusUnlockForLogging();
+  // Updates the user pod on the lock screen for the user associated with
+  // the service to reflect the provided Smart Lock state.
+  void UpdateSmartLockState(SmartLockState state);
 
-  // Retrieves the remote device list stored for the account in
-  // |proximity_auth_system_|.
-  const multidevice::RemoteDeviceRefList GetRemoteDevicesForTesting() const;
-
- protected:
-  EasyUnlockService(Profile* profile,
-                    secure_channel::SecureChannelClient* secure_channel_client);
-  ~EasyUnlockService() override;
-
-  // Does a service type specific initialization.
-  virtual void InitializeInternal() = 0;
-
-  // Does a service type specific shutdown. Called from `Shutdown`.
-  virtual void ShutdownInternal() = 0;
-
-  // Service type specific tests for whether the service is allowed. Returns
-  // false if service is not allowed. If true is returned, the service may still
-  // not be allowed if common tests fail (e.g. if Bluetooth is not available).
-  virtual bool IsAllowedInternal() const = 0;
-
-  // Called when the local device resumes after a suspend.
-  virtual void OnSuspendDoneInternal() = 0;
-
-  // Called when the user enters password before easy unlock succeeds or fails
-  // definitively.
-  virtual void OnUserEnteredPassword();
+ private:
+  friend class EasyUnlockServiceTest;
 
   // KeyedService:
   void Shutdown() override;
@@ -157,34 +156,56 @@
                            screen_type) override;
   void OnScreenDidUnlock(
       proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type)
-      override = 0;
-  void OnFocusedUserChanged(const AccountId& account_id) override = 0;
+      override;
+  void OnFocusedUserChanged(const AccountId& account_id) override;
+
+  // device_sync::DeviceSyncClient::Observer:
+  void OnReady() override;
+  void OnEnrollmentFinished() override;
+  void OnNewDevicesSynced() override;
+
+  // multidevice_setup::MultiDeviceSetupClient::Observer:
+  void OnFeatureStatesChanged(
+      const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
+          feature_states_map) override;
+
+  // Returns the authentication event for a recent password unlock,
+  // according to the current state of the service.
+  EasyUnlockAuthEvent GetPasswordAuthEvent() const;
+
+  // Returns the authentication event for a recent password unlock,
+  // according to the current state of the service.
+  SmartLockMetricsRecorder::SmartLockAuthEventPasswordState
+  GetSmartUnlockPasswordAuthEvent() const;
+
+  multidevice::RemoteDeviceRefList GetUnlockKeys();
+
+  // Determines whether failure to unlock with phone should be handled as an
+  // authentication failure.
+  bool IsSmartLockStateValidOnRemoteAuthFailure() const;
+
+  // Loads the RemoteDevice instances that will be supplied to
+  // ProximityAuthSystem.
+  void LoadRemoteDevices();
+
+  void NotifySmartLockAuthResult(bool success);
+
+  // Called when the system resumes from a suspended state.
+  void OnSuspendDone();
+
+  // Called when the user enters password before easy unlock succeeds or fails
+  // definitively.
+  void OnUserEnteredPassword();
+
+  // Updates the service to state for handling system suspend.
+  void PrepareForSuspend();
 
   // Exposes the profile to which the service is attached to subclasses.
   Profile* profile() const { return profile_; }
 
-  // Checks whether Easy unlock should be running and updates app state.
-  void UpdateAppState();
-
-  // Fill in the UI with the state returned by GetInitialSmartLockState().
-  void ShowInitialSmartLockState();
-
   // Resets the Smart Lock state set by this service.
   void ResetSmartLockState();
 
-  const SmartLockStateHandler* smartlock_state_handler() const {
-    return smartlock_state_handler_.get();
-  }
-
-  // Returns the authentication event for a recent password sign-in or unlock,
-  // according to the current state of the service.
-  EasyUnlockAuthEvent GetPasswordAuthEvent() const;
-
-  // Returns the authentication event for a recent password sign-in or unlock,
-  // according to the current state of the service.
-  SmartLockMetricsRecorder::SmartLockAuthEventPasswordState
-  GetSmartUnlockPasswordAuthEvent() const;
-
   // Called by subclasses when remote devices allowed to unlock the screen
   // are loaded for `account_id`.
   void SetProximityAuthDevices(
@@ -192,55 +213,56 @@
       const multidevice::RemoteDeviceRefList& remote_devices,
       absl::optional<multidevice::RemoteDeviceRef> local_device);
 
-  bool will_authenticate_using_easy_unlock() const {
-    return will_authenticate_using_easy_unlock_;
-  }
-
   void set_will_authenticate_using_easy_unlock(
       bool will_authenticate_using_easy_unlock) {
     will_authenticate_using_easy_unlock_ = will_authenticate_using_easy_unlock;
   }
 
- private:
+  void ShowChromebookAddedNotification();
+
+  // Fill in the UI with the state returned by GetInitialSmartLockState().
+  void ShowInitialSmartLockState();
+
+  void ShowNotificationIfNewDevicePresent(
+      const std::set<std::string>& public_keys_before_sync,
+      const std::set<std::string>& public_keys_after_sync);
+
+  // Called when ready to begin recording Smart Lock feature usage
+  // within Standard Feature Usage Logging (SFUL) framework.
+  void StartFeatureUsageMetrics();
+
+  // Called when ready to stop recording Smart Lock feature usage
+  // within SFUL framework.
+  void StopFeatureUsageMetrics();
+
+  // Checks whether Easy unlock should be running and updates app state.
+  void UpdateAppState();
+
+  void UseLoadedRemoteDevices(
+      const multidevice::RemoteDeviceRefList& remote_devices);
+
+  bool will_authenticate_using_easy_unlock() const {
+    return will_authenticate_using_easy_unlock_;
+  }
+
   // True if the user just authenticated using Easy Unlock. Reset once
-  // the screen signs in/unlocks. Used to distinguish Easy Unlock-powered
-  // signins/unlocks from password-based unlocks for metrics.
+  // the screen unlocks. Used to distinguish Easy Unlock-powered
+  // unlocks from password-based unlocks for metrics.
   bool will_authenticate_using_easy_unlock_ = false;
 
-  // Gets `smartlock_state_handler_`. Returns NULL if Easy Unlock is not
-  // allowed. Otherwise, if `smartlock_state_handler_` is not set, an instance
-  // is created. Do not cache the returned value, as it may go away if Easy
-  // Unlock gets disabled.
-  SmartLockStateHandler* GetSmartLockStateHandler();
-
-  // Updates the service to state for handling system suspend.
-  void PrepareForSuspend();
-
-  // Called when the system resumes from a suspended state.
-  void OnSuspendDone();
-
-  // Determines whether failure to unlock with phone should be handled as an
-  // authentication failure.
-  bool IsSmartLockStateValidOnRemoteAuthFailure() const;
-
-  void NotifySmartLockAuthResult(bool success);
-
   const raw_ptr<Profile, ExperimentalAsh> profile_;
   raw_ptr<secure_channel::SecureChannelClient, ExperimentalAsh>
       secure_channel_client_;
 
   ChromeProximityAuthClient proximity_auth_client_;
 
-  // Created lazily in `GetSmartLockStateHandler`.
-  std::unique_ptr<SmartLockStateHandler> smartlock_state_handler_;
-
   absl::optional<SmartLockState> smart_lock_state_;
 
   // The handler for the current auth attempt. Set iff an auth attempt is in
   // progress.
   std::unique_ptr<EasyUnlockAuthAttempt> auth_attempt_;
 
-  // Handles connecting, authenticating, and updating the UI on the lock/sign-in
+  // Handles connecting, authenticating, and updating the UI on the lock
   // screen. After a `RemoteDeviceRef` instance is provided, this object will
   // handle the rest.
   std::unique_ptr<proximity_auth::ProximityAuthSystem> proximity_auth_system_;
@@ -252,6 +274,48 @@
   // Whether the service has been shut down.
   bool shut_down_;
 
+  // The timestamp for the most recent time when the lock screen was shown. The
+  // lock screen is typically shown when the user awakens their computer from
+  // sleep -- e.g. by opening the lid -- but can also be shown if the screen is
+  // locked but the computer does not go to sleep.
+  base::TimeTicks lock_screen_last_shown_timestamp_;
+
+  // Manager responsible for handling the prefs used by proximity_auth classes.
+  std::unique_ptr<proximity_auth::ProximityAuthProfilePrefManager>
+      pref_manager_;
+
+  // If a new RemoteDevice was synced while the screen is locked, we defer
+  // loading the RemoteDevice until the screen is unlocked. For security,
+  // this deferment prevents the lock screen from being changed by a network
+  // event.
+  bool deferring_device_load_ = false;
+
+  // Responsible for showing all the notifications used for EasyUnlock.
+  std::unique_ptr<EasyUnlockNotificationController> notification_controller_;
+
+  // Used to fetch local device and remote device data.
+  raw_ptr<device_sync::DeviceSyncClient, DanglingUntriaged | ExperimentalAsh>
+      device_sync_client_;
+
+  // Used to determine the FeatureState of Smart Lock.
+  raw_ptr<multidevice_setup::MultiDeviceSetupClient,
+          DanglingUntriaged | ExperimentalAsh>
+      multidevice_setup_client_;
+
+  // Tracks Smart Lock feature usage for the Standard Feature Usage Logging
+  // (SFUL) framework.
+  std::unique_ptr<SmartLockFeatureUsageMetrics> feature_usage_metrics_;
+
+  // Stores the unlock keys for EasyUnlock before the current device sync, so we
+  // can compare it to the unlock keys after syncing.
+  std::vector<cryptauth::ExternalDeviceInfo> unlock_keys_before_sync_;
+  multidevice::RemoteDeviceRefList remote_device_unlock_keys_before_sync_;
+
+  // True if the pairing changed notification was shown, so that the next time
+  // the Chromebook is unlocked, we can show the subsequent 'pairing applied'
+  // notification.
+  bool shown_pairing_changed_notification_ = false;
+
   base::WeakPtrFactory<EasyUnlockService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.cc
index ca61df8..f17b7f1 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.cc
@@ -9,7 +9,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/ash/device_sync/device_sync_client_factory.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h"
 #include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/secure_channel/secure_channel_client_provider.h"
@@ -79,9 +78,7 @@
     return nullptr;
   }
 
-  // This is a user and primary profile, so this service serves the lock screen
-  // and manages the Smart Lock user flow for only one user.
-  EasyUnlockService* service = new EasyUnlockServiceRegular(
+  EasyUnlockService* service = new EasyUnlockService(
       Profile::FromBrowserContext(context),
       secure_channel::SecureChannelClientProvider::GetInstance()->GetClient(),
       device_sync::DeviceSyncClientFactory::GetForProfile(profile),
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc
deleted file mode 100644
index 8a2d505..0000000
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc
+++ /dev/null
@@ -1,424 +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.
-
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h"
-
-#include "base/metrics/histogram_macros.h"
-#include "build/build_config.h"
-#include "chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_notification_controller.h"
-#include "chrome/browser/ash/login/easy_unlock/smartlock_feature_usage_metrics.h"
-#include "chrome/browser/ash/login/session/user_session_manager.h"
-#include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/gcm/gcm_profile_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.h"
-#include "chrome/common/extensions/extension_constants.h"
-#include "chrome/common/pref_names.h"
-#include "chromeos/ash/components/multidevice/logging/logging.h"
-#include "chromeos/ash/components/proximity_auth/proximity_auth_pref_names.h"
-#include "chromeos/ash/components/proximity_auth/proximity_auth_profile_pref_manager.h"
-#include "chromeos/ash/components/proximity_auth/proximity_auth_system.h"
-#include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
-#include "chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h"
-#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
-#include "components/gcm_driver/gcm_profile_service.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/pref_service.h"
-#include "components/translate/core/browser/translate_download_manager.h"
-#include "components/user_manager/user_manager.h"
-#include "components/version_info/version_info.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/common/content_switches.h"
-#include "extensions/browser/event_router.h"
-#include "extensions/common/constants.h"
-#include "google_apis/gaia/gaia_auth_util.h"
-
-namespace ash {
-
-namespace {
-
-enum class SmartLockToggleFeature { DISABLE = false, ENABLE = true };
-
-// The result of a SmartLock operation.
-enum class SmartLockResult { FAILURE = false, SUCCESS = true };
-
-enum class SmartLockEnabledState {
-  ENABLED = 0,
-  DISABLED = 1,
-  UNSET = 2,
-  COUNT
-};
-
-void LogSmartLockEnabledState(SmartLockEnabledState state) {
-  UMA_HISTOGRAM_ENUMERATION("SmartLock.EnabledState", state,
-                            SmartLockEnabledState::COUNT);
-}
-
-}  // namespace
-
-EasyUnlockServiceRegular::EasyUnlockServiceRegular(
-    Profile* profile,
-    secure_channel::SecureChannelClient* secure_channel_client,
-    device_sync::DeviceSyncClient* device_sync_client,
-    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
-    : EasyUnlockServiceRegular(
-          profile,
-          secure_channel_client,
-          std::make_unique<EasyUnlockNotificationController>(profile),
-          device_sync_client,
-          multidevice_setup_client) {}
-
-EasyUnlockServiceRegular::EasyUnlockServiceRegular(
-    Profile* profile,
-    secure_channel::SecureChannelClient* secure_channel_client,
-    std::unique_ptr<EasyUnlockNotificationController> notification_controller,
-    device_sync::DeviceSyncClient* device_sync_client,
-    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
-    : EasyUnlockService(profile, secure_channel_client),
-      lock_screen_last_shown_timestamp_(base::TimeTicks::Now()),
-      notification_controller_(std::move(notification_controller)),
-      device_sync_client_(device_sync_client),
-      multidevice_setup_client_(multidevice_setup_client) {}
-
-EasyUnlockServiceRegular::~EasyUnlockServiceRegular() = default;
-
-// TODO(jhawkins): This method with `has_unlock_keys` == true is the only signal
-// that SmartLock setup has completed successfully. Make this signal more
-// explicit.
-void EasyUnlockServiceRegular::LoadRemoteDevices() {
-  if (!device_sync_client_->is_ready()) {
-    // OnEnrollmentFinished() or OnNewDevicesSynced() will call back on this
-    // method once `device_sync_client_` is ready.
-    PA_LOG(VERBOSE) << "DeviceSyncClient is not ready yet, delaying "
-                       "UseLoadedRemoteDevices().";
-    return;
-  }
-
-  if (!IsEnabled()) {
-    // OnFeatureStatesChanged() will call back on this method when feature state
-    // changes.
-    PA_LOG(VERBOSE) << "Smart Lock is not enabled by user; aborting.";
-    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
-                            absl::nullopt /* local_device */);
-    return;
-  }
-
-  bool has_unlock_keys = !GetUnlockKeys().empty();
-
-  // TODO(jhawkins): The enabled pref should not be tied to whether unlock keys
-  // exist; instead, both of these variables should be used to determine
-  // IsEnabled().
-  pref_manager_->SetIsEasyUnlockEnabled(has_unlock_keys);
-  if (has_unlock_keys) {
-    // If `has_unlock_keys` is true, then the user must have successfully
-    // completed setup. Track that the IsEasyUnlockEnabled pref is actively set
-    // by the user, as opposed to passively being set to disabled (the default
-    // state).
-    pref_manager_->SetEasyUnlockEnabledStateSet();
-    LogSmartLockEnabledState(SmartLockEnabledState::ENABLED);
-  } else {
-    PA_LOG(ERROR) << "Smart Lock is enabled by user, but no unlock key is "
-                     "present; aborting.";
-    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
-                            absl::nullopt /* local_device */);
-
-    if (pref_manager_->IsEasyUnlockEnabledStateSet()) {
-      LogSmartLockEnabledState(SmartLockEnabledState::DISABLED);
-    } else {
-      LogSmartLockEnabledState(SmartLockEnabledState::UNSET);
-    }
-    return;
-  }
-
-  // This code path may be hit by:
-  //   1. New devices were synced on the lock screen.
-  //   2. The service was initialized while the login screen is still up.
-  if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) {
-    PA_LOG(VERBOSE) << "Deferring device load until screen is unlocked.";
-    deferring_device_load_ = true;
-    return;
-  }
-
-  UseLoadedRemoteDevices(GetUnlockKeys());
-}
-
-void EasyUnlockServiceRegular::UseLoadedRemoteDevices(
-    const multidevice::RemoteDeviceRefList& remote_devices) {
-  // When EasyUnlock is enabled, only one EasyUnlock host should exist.
-  if (remote_devices.size() != 1u) {
-    PA_LOG(ERROR) << "There should only be 1 Smart Lock host, but there are: "
-                  << remote_devices.size();
-    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
-                            absl::nullopt);
-    NOTREACHED();
-    return;
-  }
-
-  absl::optional<multidevice::RemoteDeviceRef> local_device =
-      device_sync_client_->GetLocalDeviceMetadata();
-  if (!local_device) {
-    PA_LOG(ERROR) << "EasyUnlockServiceRegular::" << __func__
-                  << ": Local device unexpectedly null.";
-    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
-                            absl::nullopt);
-    return;
-  }
-
-  SetProximityAuthDevices(GetAccountId(), remote_devices, local_device);
-}
-
-proximity_auth::ProximityAuthPrefManager*
-EasyUnlockServiceRegular::GetProximityAuthPrefManager() {
-  return pref_manager_.get();
-}
-
-AccountId EasyUnlockServiceRegular::GetAccountId() const {
-  const user_manager::User* const primary_user =
-      user_manager::UserManager::Get()->GetPrimaryUser();
-  DCHECK(primary_user);
-  return primary_user->GetAccountId();
-}
-
-void EasyUnlockServiceRegular::InitializeInternal() {
-  pref_manager_ =
-      std::make_unique<proximity_auth::ProximityAuthProfilePrefManager>(
-          profile()->GetPrefs(), multidevice_setup_client_);
-
-  // If `device_sync_client_` is not ready yet, wait for it to call back on
-  // OnReady().
-  if (device_sync_client_->is_ready())
-    OnReady();
-  device_sync_client_->AddObserver(this);
-
-  OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates());
-  multidevice_setup_client_->AddObserver(this);
-  StartFeatureUsageMetrics();
-
-  LoadRemoteDevices();
-}
-
-void EasyUnlockServiceRegular::ShutdownInternal() {
-  pref_manager_.reset();
-  notification_controller_.reset();
-
-  device_sync_client_->RemoveObserver(this);
-
-  multidevice_setup_client_->RemoveObserver(this);
-
-  StopFeatureUsageMetrics();
-
-  weak_ptr_factory_.InvalidateWeakPtrs();
-}
-
-bool EasyUnlockServiceRegular::IsAllowedInternal() const {
-  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
-  if (!user_manager->IsLoggedInAsUserWithGaiaAccount())
-    return false;
-
-  // TODO(tengs): Ephemeral accounts generate a new enrollment every time they
-  // are added, so disable Smart Lock to reduce enrollments on server. However,
-  // ephemeral accounts can be locked, so we should revisit this use case.
-  if (user_manager->IsCurrentUserNonCryptohomeDataEphemeral())
-    return false;
-
-  if (!ProfileHelper::IsPrimaryProfile(profile()))
-    return false;
-
-  if (multidevice_setup_client_->GetFeatureState(
-          multidevice_setup::mojom::Feature::kSmartLock) ==
-      multidevice_setup::mojom::FeatureState::kProhibitedByPolicy) {
-    return false;
-  }
-
-  return true;
-}
-
-bool EasyUnlockServiceRegular::IsEnabled() const {
-  return multidevice_setup_client_->GetFeatureState(
-             multidevice_setup::mojom::Feature::kSmartLock) ==
-         multidevice_setup::mojom::FeatureState::kEnabledByUser;
-}
-
-void EasyUnlockServiceRegular::OnSuspendDoneInternal() {
-  lock_screen_last_shown_timestamp_ = base::TimeTicks::Now();
-}
-
-void EasyUnlockServiceRegular::OnReady() {
-  // If the local device and synced devices are ready for the first time,
-  // establish what the unlock keys were before the next sync. This is necessary
-  // in order for OnNewDevicesSynced() to determine if new devices were added
-  // since the last sync.
-  remote_device_unlock_keys_before_sync_ = GetUnlockKeys();
-}
-
-void EasyUnlockServiceRegular::OnEnrollmentFinished() {
-  // The local device may be ready for the first time, or it may have been
-  // updated, so reload devices.
-  LoadRemoteDevices();
-}
-
-void EasyUnlockServiceRegular::OnNewDevicesSynced() {
-  std::set<std::string> public_keys_before_sync;
-  for (const auto& remote_device : remote_device_unlock_keys_before_sync_) {
-    public_keys_before_sync.insert(remote_device.public_key());
-  }
-
-  multidevice::RemoteDeviceRefList remote_device_unlock_keys_after_sync =
-      GetUnlockKeys();
-  std::set<std::string> public_keys_after_sync;
-  for (const auto& remote_device : remote_device_unlock_keys_after_sync) {
-    public_keys_after_sync.insert(remote_device.public_key());
-  }
-
-  ShowNotificationIfNewDevicePresent(public_keys_before_sync,
-                                     public_keys_after_sync);
-
-  LoadRemoteDevices();
-
-  remote_device_unlock_keys_before_sync_ = remote_device_unlock_keys_after_sync;
-}
-
-void EasyUnlockServiceRegular::OnFeatureStatesChanged(
-    const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
-        feature_states_map) {
-  LoadRemoteDevices();
-  UpdateAppState();
-}
-
-void EasyUnlockServiceRegular::ShowChromebookAddedNotification() {
-  // The user may have decided to disable Smart Lock or the whole multidevice
-  // suite immediately after completing setup, so ensure that Smart Lock is
-  // enabled.
-  if (IsEnabled())
-    notification_controller_->ShowChromebookAddedNotification();
-}
-
-void EasyUnlockServiceRegular::ShowNotificationIfNewDevicePresent(
-    const std::set<std::string>& public_keys_before_sync,
-    const std::set<std::string>& public_keys_after_sync) {
-  if (public_keys_before_sync == public_keys_after_sync)
-    return;
-
-  // Show the appropriate notification if an unlock key is first synced or if it
-  // changes an existing key.
-  // Note: We do not show a notification when EasyUnlock is disabled by sync nor
-  // if EasyUnlock was enabled through the setup app.
-  if (!public_keys_after_sync.empty()) {
-    if (public_keys_before_sync.empty()) {
-      multidevice_setup::MultiDeviceSetupDialog* multidevice_setup_dialog =
-          multidevice_setup::MultiDeviceSetupDialog::Get();
-      if (multidevice_setup_dialog) {
-        // Delay showing the "Chromebook added" notification until the
-        // MultiDeviceSetupDialog is closed.
-        multidevice_setup_dialog->AddOnCloseCallback(base::BindOnce(
-            &EasyUnlockServiceRegular::ShowChromebookAddedNotification,
-            weak_ptr_factory_.GetWeakPtr()));
-        return;
-      }
-
-      notification_controller_->ShowChromebookAddedNotification();
-    } else {
-      shown_pairing_changed_notification_ = true;
-      notification_controller_->ShowPairingChangeNotification();
-    }
-  }
-}
-
-void EasyUnlockServiceRegular::StartFeatureUsageMetrics() {
-  feature_usage_metrics_ =
-      std::make_unique<SmartLockFeatureUsageMetrics>(multidevice_setup_client_);
-
-  SmartLockMetricsRecorder::SetUsageRecorderInstance(
-      feature_usage_metrics_.get());
-}
-
-void EasyUnlockServiceRegular::StopFeatureUsageMetrics() {
-  feature_usage_metrics_.reset();
-  SmartLockMetricsRecorder::SetUsageRecorderInstance(nullptr);
-}
-
-void EasyUnlockServiceRegular::OnScreenDidLock(
-    proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
-  EasyUnlockService::OnScreenDidLock(screen_type);
-
-  set_will_authenticate_using_easy_unlock(false);
-  lock_screen_last_shown_timestamp_ = base::TimeTicks::Now();
-}
-
-void EasyUnlockServiceRegular::OnScreenDidUnlock(
-    proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
-  // If we tried to load remote devices (e.g. after a sync or the
-  // service was initialized) while the screen was locked, we can now
-  // load the new remote devices.
-  //
-  // It's important to go through this code path even if unlocking the
-  // login screen. Because when the service is initialized while the
-  // user is signing in we need to load the remotes. Otherwise, the
-  // first time the user locks the screen the feature won't work.
-  if (deferring_device_load_) {
-    PA_LOG(VERBOSE) << "Loading deferred devices after screen unlock.";
-    deferring_device_load_ = false;
-    LoadRemoteDevices();
-  }
-
-  // Do not process events for the login screen.
-  if (screen_type != proximity_auth::ScreenlockBridge::LockHandler::LOCK_SCREEN)
-    return;
-
-  if (shown_pairing_changed_notification_) {
-    shown_pairing_changed_notification_ = false;
-
-    if (!GetUnlockKeys().empty()) {
-      notification_controller_->ShowPairingChangeAppliedNotification(
-          GetUnlockKeys()[0].name());
-    }
-  }
-
-  // Only record metrics for users who have enabled the feature.
-  if (IsEnabled()) {
-    EasyUnlockAuthEvent event = will_authenticate_using_easy_unlock()
-                                    ? EASY_UNLOCK_SUCCESS
-                                    : GetPasswordAuthEvent();
-    RecordEasyUnlockScreenUnlockEvent(event);
-
-    if (will_authenticate_using_easy_unlock()) {
-      // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric.
-      SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
-          SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kSmartLock);
-      SmartLockMetricsRecorder::RecordAuthResultUnlockSuccess();
-      RecordEasyUnlockScreenUnlockDuration(base::TimeTicks::Now() -
-                                           lock_screen_last_shown_timestamp_);
-    } else {
-      SmartLockMetricsRecorder::RecordAuthMethodChoiceUnlockPasswordState(
-          GetSmartUnlockPasswordAuthEvent());
-      // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric.
-      SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
-          SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kOther);
-      OnUserEnteredPassword();
-    }
-  }
-
-  set_will_authenticate_using_easy_unlock(false);
-}
-
-void EasyUnlockServiceRegular::OnFocusedUserChanged(
-    const AccountId& account_id) {
-  // Nothing to do.
-}
-
-multidevice::RemoteDeviceRefList EasyUnlockServiceRegular::GetUnlockKeys() {
-  multidevice::RemoteDeviceRefList unlock_keys;
-  for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
-    bool unlock_key = remote_device.GetSoftwareFeatureState(
-                          multidevice::SoftwareFeature::kSmartLockHost) ==
-                      multidevice::SoftwareFeatureState::kEnabled;
-    if (unlock_key)
-      unlock_keys.push_back(remote_device);
-  }
-  return unlock_keys;
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h
deleted file mode 100644
index 52cd30d..0000000
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h
+++ /dev/null
@@ -1,165 +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.
-
-#ifndef CHROME_BROWSER_ASH_LOGIN_EASY_UNLOCK_EASY_UNLOCK_SERVICE_REGULAR_H_
-#define CHROME_BROWSER_ASH_LOGIN_EASY_UNLOCK_EASY_UNLOCK_SERVICE_REGULAR_H_
-
-#include <memory>
-#include <string>
-
-#include "base/functional/callback.h"
-#include "base/memory/raw_ptr.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
-#include "chromeos/ash/components/multidevice/remote_device_ref.h"
-#include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
-#include "chromeos/ash/services/device_sync/proto/cryptauth_api.pb.h"
-#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
-#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
-
-namespace proximity_auth {
-class ProximityAuthProfilePrefManager;
-}  // namespace proximity_auth
-
-class Profile;
-
-namespace ash {
-
-class EasyUnlockNotificationController;
-class SmartLockFeatureUsageMetrics;
-
-namespace secure_channel {
-class SecureChannelClient;
-}
-
-// EasyUnlockService instance that should be used for regular, non-signin
-// profiles.
-class EasyUnlockServiceRegular
-    : public EasyUnlockService,
-      public device_sync::DeviceSyncClient::Observer,
-      public multidevice_setup::MultiDeviceSetupClient::Observer {
- public:
-  EasyUnlockServiceRegular(
-      Profile* profile,
-      secure_channel::SecureChannelClient* secure_channel_client,
-      device_sync::DeviceSyncClient* device_sync_client,
-      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
-
-  // Constructor for tests.
-  EasyUnlockServiceRegular(
-      Profile* profile,
-      secure_channel::SecureChannelClient* secure_channel_client,
-      std::unique_ptr<EasyUnlockNotificationController> notification_controller,
-      device_sync::DeviceSyncClient* device_sync_client,
-      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
-
-  EasyUnlockServiceRegular(const EasyUnlockServiceRegular&) = delete;
-  EasyUnlockServiceRegular& operator=(const EasyUnlockServiceRegular&) = delete;
-
-  ~EasyUnlockServiceRegular() override;
-
- private:
-  friend class EasyUnlockServiceRegularTest;
-
-  // Loads the RemoteDevice instances that will be supplied to
-  // ProximityAuthSystem.
-  void LoadRemoteDevices();
-
-  void UseLoadedRemoteDevices(
-      const multidevice::RemoteDeviceRefList& remote_devices);
-
-  // EasyUnlockService implementation:
-  proximity_auth::ProximityAuthPrefManager* GetProximityAuthPrefManager()
-      override;
-  AccountId GetAccountId() const override;
-  void InitializeInternal() override;
-  void ShutdownInternal() override;
-  bool IsAllowedInternal() const override;
-  bool IsEnabled() const override;
-
-  void OnSuspendDoneInternal() override;
-
-  // device_sync::DeviceSyncClient::Observer:
-  void OnReady() override;
-  void OnEnrollmentFinished() override;
-  void OnNewDevicesSynced() override;
-
-  // multidevice_setup::MultiDeviceSetupClient::Observer:
-  void OnFeatureStatesChanged(
-      const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
-          feature_states_map) override;
-
-  void ShowChromebookAddedNotification();
-
-  void ShowNotificationIfNewDevicePresent(
-      const std::set<std::string>& public_keys_before_sync,
-      const std::set<std::string>& public_keys_after_sync);
-
-  // Called when ready to begin recording Smart Lock feature usage
-  // within Standard Feature Usage Logging (SFUL) framework.
-  void StartFeatureUsageMetrics();
-
-  // Called when ready to stop recording Smart Lock feature usage
-  // within SFUL framework.
-  void StopFeatureUsageMetrics();
-
-  // EasyUnlockService:
-  void OnScreenDidLock(proximity_auth::ScreenlockBridge::LockHandler::ScreenType
-                           screen_type) override;
-  void OnScreenDidUnlock(
-      proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type)
-      override;
-  void OnFocusedUserChanged(const AccountId& account_id) override;
-
-  multidevice::RemoteDeviceRefList GetUnlockKeys();
-
-  // The timestamp for the most recent time when the lock screen was shown. The
-  // lock screen is typically shown when the user awakens their computer from
-  // sleep -- e.g. by opening the lid -- but can also be shown if the screen is
-  // locked but the computer does not go to sleep.
-  base::TimeTicks lock_screen_last_shown_timestamp_;
-
-  // Manager responsible for handling the prefs used by proximity_auth classes.
-  std::unique_ptr<proximity_auth::ProximityAuthProfilePrefManager>
-      pref_manager_;
-
-  // If a new RemoteDevice was synced while the screen is locked, we defer
-  // loading the RemoteDevice until the screen is unlocked. For security,
-  // this deferment prevents the lock screen from being changed by a network
-  // event.
-  bool deferring_device_load_ = false;
-
-  // Responsible for showing all the notifications used for EasyUnlock.
-  std::unique_ptr<EasyUnlockNotificationController> notification_controller_;
-
-  // Used to fetch local device and remote device data.
-  raw_ptr<device_sync::DeviceSyncClient, DanglingUntriaged | ExperimentalAsh>
-      device_sync_client_;
-
-  // Used to determine the FeatureState of Smart Lock.
-  raw_ptr<multidevice_setup::MultiDeviceSetupClient,
-          DanglingUntriaged | ExperimentalAsh>
-      multidevice_setup_client_;
-
-  // Tracks Smart Lock feature usage for the Standard Feature Usage Logging
-  // (SFUL) framework.
-  std::unique_ptr<SmartLockFeatureUsageMetrics> feature_usage_metrics_;
-
-  // Stores the unlock keys for EasyUnlock before the current device sync, so we
-  // can compare it to the unlock keys after syncing.
-  std::vector<cryptauth::ExternalDeviceInfo> unlock_keys_before_sync_;
-  multidevice::RemoteDeviceRefList remote_device_unlock_keys_before_sync_;
-
-  // True if the pairing changed notification was shown, so that the next time
-  // the Chromebook is unlocked, we can show the subsequent 'pairing applied'
-  // notification.
-  bool shown_pairing_changed_notification_ = false;
-
-  base::WeakPtrFactory<EasyUnlockServiceRegular> weak_ptr_factory_{this};
-};
-
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_LOGIN_EASY_UNLOCK_EASY_UNLOCK_SERVICE_REGULAR_H_
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular_unittest.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_unittest.cc
similarity index 82%
rename from chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular_unittest.cc
rename to chrome/browser/ash/login/easy_unlock/easy_unlock_service_unittest.cc
index 0a13580..35d6db19 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular_unittest.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_unittest.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/memory/raw_ptr.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
 
 #include <stddef.h>
@@ -16,13 +15,13 @@
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_notification_controller.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h"
 #include "chrome/browser/ash/login/easy_unlock/smartlock_feature_usage_metrics.h"
 #include "chrome/browser/ash/login/session/chrome_session_manager.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
@@ -158,14 +157,13 @@
 
 }  // namespace
 
-class EasyUnlockServiceRegularTest : public testing::Test {
+class EasyUnlockServiceTest : public testing::Test {
  public:
-  EasyUnlockServiceRegularTest(const EasyUnlockServiceRegularTest&) = delete;
-  EasyUnlockServiceRegularTest& operator=(const EasyUnlockServiceRegularTest&) =
-      delete;
+  EasyUnlockServiceTest(const EasyUnlockServiceTest&) = delete;
+  EasyUnlockServiceTest& operator=(const EasyUnlockServiceTest&) = delete;
 
  protected:
-  EasyUnlockServiceRegularTest()
+  EasyUnlockServiceTest()
       : test_local_device_(
             multidevice::RemoteDeviceRefBuilder()
                 .SetPublicKey("local device")
@@ -184,7 +182,7 @@
                 .SetBeaconSeeds(multidevice::FromCryptAuthSeedList(
                     std::vector<cryptauth::BeaconSeed>(4)))
                 .Build()) {}
-  ~EasyUnlockServiceRegularTest() override = default;
+  ~EasyUnlockServiceTest() override = default;
 
   void SetUp() override {
     display::Screen::SetScreenInstance(&test_screen_);
@@ -235,7 +233,7 @@
 
   void TearDown() override {
     SetScreenLockState(false /* is_locked */);
-    easy_unlock_service_regular_->Shutdown();
+    easy_unlock_service_->Shutdown();
     chromeos::PowerManagerClient::Shutdown();
     TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
     display::Screen::SetScreenInstance(nullptr);
@@ -256,11 +254,11 @@
         testing::StrictMock<MockEasyUnlockNotificationController>>();
     mock_notification_controller_ = mock_notification_controller.get();
 
-    easy_unlock_service_regular_ = std::make_unique<EasyUnlockServiceRegular>(
+    easy_unlock_service_ = std::make_unique<EasyUnlockService>(
         profile_.get(), fake_secure_channel_client_.get(),
         std::move(mock_notification_controller), fake_device_sync_client_.get(),
         fake_multidevice_setup_client_.get());
-    easy_unlock_service_regular_->Initialize();
+    easy_unlock_service_->Initialize();
   }
 
   void SetLocalDevice(
@@ -287,8 +285,9 @@
   }
 
   void SetScreenLockState(bool is_locked) {
-    if (is_locked)
+    if (is_locked) {
       proximity_auth::ScreenlockBridge::Get()->SetFocusedUser(account_id_);
+    }
 
     fake_lock_handler_->ClearSmartLockState();
     fake_lock_handler_->ClearSmartLockAuthResult();
@@ -299,8 +298,7 @@
 
   void VerifyGetRemoteDevices(bool are_remote_devices_expected) {
     const multidevice::RemoteDeviceRefList remote_devices =
-        static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get())
-            ->GetRemoteDevicesForTesting();
+        easy_unlock_service_->GetRemoteDevicesForTesting();
     if (are_remote_devices_expected) {
       EXPECT_FALSE(remote_devices.empty());
     } else {
@@ -317,17 +315,15 @@
   }
 
   EasyUnlockAuthEvent GetPasswordAuthEvent() {
-    return easy_unlock_service_regular_->GetPasswordAuthEvent();
+    return easy_unlock_service_->GetPasswordAuthEvent();
   }
 
   SmartLockMetricsRecorder::SmartLockAuthEventPasswordState
   GetSmartUnlockPasswordAuthEvent() {
-    return easy_unlock_service_regular_->GetSmartUnlockPasswordAuthEvent();
+    return easy_unlock_service_->GetSmartUnlockPasswordAuthEvent();
   }
 
-  void ResetSmartLockState() {
-    easy_unlock_service_regular_->ResetSmartLockState();
-  }
+  void ResetSmartLockState() { easy_unlock_service_->ResetSmartLockState(); }
 
   // Must outlive TestingProfiles.
   content::BrowserTaskEnvironment task_environment_;
@@ -351,7 +347,7 @@
   std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
       fake_multidevice_setup_client_;
   std::unique_ptr<proximity_auth::FakeLockHandler> fake_lock_handler_;
-  std::unique_ptr<EasyUnlockServiceRegular> easy_unlock_service_regular_;
+  std::unique_ptr<EasyUnlockService> easy_unlock_service_;
 
   std::string profile_gaia_id_;
 
@@ -376,35 +372,33 @@
   }
 };
 
-TEST_F(EasyUnlockServiceRegularTest, NotAllowedWhenProhibited) {
+TEST_F(EasyUnlockServiceTest, NotAllowedWhenProhibited) {
   InitializeService(true /* should_initialize_all_dependencies */);
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kSmartLock,
       multidevice_setup::mojom::FeatureState::kProhibitedByPolicy);
 
-  EXPECT_FALSE(easy_unlock_service_regular_->IsAllowed());
+  EXPECT_FALSE(easy_unlock_service_->IsAllowed());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, NotAllowedForEphemeralAccounts) {
+TEST_F(EasyUnlockServiceTest, NotAllowedForEphemeralAccounts) {
   InitializeService(true /* should_initialize_all_dependencies */);
   fake_chrome_user_manager_->set_current_user_ephemeral(true);
-  EXPECT_FALSE(easy_unlock_service_regular_->IsAllowed());
+  EXPECT_FALSE(easy_unlock_service_->IsAllowed());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetProximityAuthPrefManager) {
+TEST_F(EasyUnlockServiceTest, GetProximityAuthPrefManager) {
   InitializeService(true /* should_initialize_all_dependencies */);
 
-  EXPECT_TRUE(
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get())
-          ->GetProximityAuthPrefManager());
+  EXPECT_TRUE(easy_unlock_service_->GetProximityAuthPrefManager());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices) {
+TEST_F(EasyUnlockServiceTest, GetRemoteDevices) {
   InitializeService(true /* should_initialize_all_dependencies */);
   VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices_InitiallyNotReady) {
+TEST_F(EasyUnlockServiceTest, GetRemoteDevices_InitiallyNotReady) {
   SetIsEnabled(true /* is_enabled */);
   InitializeService(false /* should_initialize_all_dependencies */);
   VerifyGetRemoteDevices(false /* are_remote_devices_expected */);
@@ -418,8 +412,7 @@
   VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
 }
 
-TEST_F(EasyUnlockServiceRegularTest,
-       GetRemoteDevices_InitiallyNoSyncedDevices) {
+TEST_F(EasyUnlockServiceTest, GetRemoteDevices_InitiallyNoSyncedDevices) {
   fake_device_sync_client_->NotifyReady();
   SetLocalDevice(test_local_device_);
   SetSyncedDevices(multidevice::RemoteDeviceRefList() /* synced_devices */);
@@ -437,7 +430,7 @@
 // Test that the "Chromebook added" notification does not show while the
 // MultiDeviceSetupDialog is active, and only shows once it is closed.
 TEST_F(
-    EasyUnlockServiceRegularTest,
+    EasyUnlockServiceTest,
     GetRemoteDevices_InitiallyNoSyncedDevices_MultiDeviceSetupDialogVisible) {
   SetDisplaySize(gfx::Size(1920, 1200));
 
@@ -473,7 +466,7 @@
   multidevice_setup::MultiDeviceSetupDialog::SetInstanceForTesting(nullptr);
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices_InitiallyNotEnabled) {
+TEST_F(EasyUnlockServiceTest, GetRemoteDevices_InitiallyNotEnabled) {
   fake_device_sync_client_->NotifyReady();
   SetLocalDevice(test_local_device_);
   SetSyncedDevices(test_remote_devices_);
@@ -485,7 +478,7 @@
   VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
 }
 
-TEST_F(EasyUnlockServiceRegularTest,
+TEST_F(EasyUnlockServiceTest,
        GetRemoteDevices_DeferDeviceLoadUntilScreenIsUnlocked) {
   SetScreenLockState(true /* is_locked */);
   InitializeService(true /* should_initialize_all_dependencies */);
@@ -495,7 +488,7 @@
   VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices_SmartLockHostChanged) {
+TEST_F(EasyUnlockServiceTest, GetRemoteDevices_SmartLockHostChanged) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetScreenLockState(true /* is_locked */);
   VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
@@ -523,19 +516,17 @@
 // that the success metric is emitted.
 // The "SmartLock.AuthResult" Failure bucket is incorrectly emitted to during
 // this test. See crbug.com/1255964 for more info.
-TEST_F(EasyUnlockServiceRegularTest, AuthenticateWithEasyUnlock) {
+TEST_F(EasyUnlockServiceTest, AuthenticateWithEasyUnlock) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetScreenLockState(true /* is_locked */);
 
-  auto* service =
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
-
-  // service->AttemptAuth() will fail if the SmartLockState is not
+  // easy_unlock_service_->AttemptAuth() will fail if the SmartLockState is not
   // kPhoneAuthenticated.
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
 
-  EXPECT_TRUE(service->AttemptAuth(account_id_));
-  service->FinalizeUnlock(true);
+  EXPECT_TRUE(easy_unlock_service_->AttemptAuth(account_id_));
+  easy_unlock_service_->FinalizeUnlock(true);
 
   histogram_tester_.ExpectBucketCount("SmartLock.AuthResult.Unlock", 1, 0);
   histogram_tester_.ExpectBucketCount(
@@ -553,23 +544,21 @@
 // Regression test for crbug.com/974410.
 // The "SmartLock.AuthResult" Failure bucket is incorrectly emitted to during
 // this test. See crbug.com/1255964 for more info.
-TEST_F(EasyUnlockServiceRegularTest, AuthenticateWithEasyUnlockMultipleTimes) {
+TEST_F(EasyUnlockServiceTest, AuthenticateWithEasyUnlockMultipleTimes) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetScreenLockState(true /* is_locked */);
 
-  auto* service =
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
-
-  // service->AttemptAuth() will fail if the SmartLockState is not
+  // easy_unlock_service_->AttemptAuth() will fail if the SmartLockState is not
   // kPhoneAuthenticated.
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
 
-  EXPECT_TRUE(service->AttemptAuth(account_id_));
-  service->FinalizeUnlock(true);
+  EXPECT_TRUE(easy_unlock_service_->AttemptAuth(account_id_));
+  easy_unlock_service_->FinalizeUnlock(true);
 
   // The first auth attempt is still ongoing. A second auth attempt request
   // should be rejected.
-  EXPECT_FALSE(service->AttemptAuth(account_id_));
+  EXPECT_FALSE(easy_unlock_service_->AttemptAuth(account_id_));
 
   histogram_tester_.ExpectBucketCount("SmartLock.AuthResult.Unlock", 1, 0);
   histogram_tester_.ExpectBucketCount(
@@ -590,32 +579,30 @@
 // ProximityAuthClient during the OnScreenDidUnlock event. Check that the second
 // call to AttemptAuth succeeds since UpdateSmartLockState clears out the last
 // auth attempt, allowing the next auth attempt to proceed.
-TEST_F(EasyUnlockServiceRegularTest,
-       UpdateSmartLockStateClearsLastAuthAttempt) {
+TEST_F(EasyUnlockServiceTest, UpdateSmartLockStateClearsLastAuthAttempt) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetScreenLockState(true /* is_locked */);
 
-  auto* service =
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
 
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
-
-  EXPECT_TRUE(service->AttemptAuth(account_id_));
-  service->FinalizeUnlock(true);
+  EXPECT_TRUE(easy_unlock_service_->AttemptAuth(account_id_));
+  easy_unlock_service_->FinalizeUnlock(true);
 
   ASSERT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
   EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().value());
 
   SetScreenLockState(false /* is_locked */);
-  service->UpdateSmartLockState(SmartLockState::kInactive);
+  easy_unlock_service_->UpdateSmartLockState(SmartLockState::kInactive);
 
   EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().has_value());
   EXPECT_FALSE(fake_lock_handler_->smart_lock_state().has_value());
 
   SetScreenLockState(true /* is_locked */);
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
-  EXPECT_TRUE(service->AttemptAuth(account_id_));
-  service->FinalizeUnlock(true);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
+  EXPECT_TRUE(easy_unlock_service_->AttemptAuth(account_id_));
+  easy_unlock_service_->FinalizeUnlock(true);
 
   ASSERT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
   EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().value());
@@ -623,27 +610,24 @@
   SetScreenLockState(false /* is_locked */);
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetInitialSmartLockState_FeatureEnabled) {
+TEST_F(EasyUnlockServiceTest, GetInitialSmartLockState_FeatureEnabled) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetScreenLockState(true /* is_locked */);
 
-  auto* service =
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
   EXPECT_EQ(SmartLockState::kConnectingToPhone,
-            service->GetInitialSmartLockState());
+            easy_unlock_service_->GetInitialSmartLockState());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetInitialSmartLockState_FeatureDisabled) {
+TEST_F(EasyUnlockServiceTest, GetInitialSmartLockState_FeatureDisabled) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetIsEnabled(false);
   SetScreenLockState(true /* is_locked */);
 
-  auto* service =
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
-  EXPECT_EQ(SmartLockState::kDisabled, service->GetInitialSmartLockState());
+  EXPECT_EQ(SmartLockState::kDisabled,
+            easy_unlock_service_->GetInitialSmartLockState());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, ShowInitialSmartLockState_FeatureEnabled) {
+TEST_F(EasyUnlockServiceTest, ShowInitialSmartLockState_FeatureEnabled) {
   InitializeService(true /* should_initialize_all_dependencies */);
 
   EXPECT_FALSE(fake_lock_handler_->smart_lock_state());
@@ -656,15 +640,13 @@
   EXPECT_EQ(SmartLockState::kConnectingToPhone,
             fake_lock_handler_->smart_lock_state().value());
 
-  auto* service =
-      static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
-  service->UpdateSmartLockState(SmartLockState::kConnectingToPhone);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kConnectingToPhone);
   EXPECT_EQ(SmartLockState::kConnectingToPhone,
             fake_lock_handler_->smart_lock_state().value());
 }
 
-TEST_F(EasyUnlockServiceRegularTest,
-       ShowInitialSmartLockState_FeatureDisabled) {
+TEST_F(EasyUnlockServiceTest, ShowInitialSmartLockState_FeatureDisabled) {
   InitializeService(true /* should_initialize_all_dependencies */);
   SetIsEnabled(false);
 
@@ -679,11 +661,11 @@
             fake_lock_handler_->smart_lock_state().value());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, PrepareForSuspend) {
+TEST_F(EasyUnlockServiceTest, PrepareForSuspend) {
   InitializeService(/*should_initialize_all_dependencies=*/true);
   SetScreenLockState(/*is_locked=*/true);
-  EasyUnlockService* service = easy_unlock_service_regular_.get();
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
   EXPECT_EQ(SmartLockState::kPhoneAuthenticated,
             fake_lock_handler_->smart_lock_state().value());
   chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
@@ -692,34 +674,35 @@
             fake_lock_handler_->smart_lock_state().value());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, HandleAuthFailureInUpdateSmartLockState) {
+TEST_F(EasyUnlockServiceTest, HandleAuthFailureInUpdateSmartLockState) {
   InitializeService(/*should_initialize_all_dependencies=*/true);
   SetScreenLockState(/*is_locked=*/true);
-  EasyUnlockService* service = easy_unlock_service_regular_.get();
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
   EXPECT_EQ(proximity_auth::mojom::AuthType::USER_CLICK,
             fake_lock_handler_->GetAuthType(account_id_));
-  service->AttemptAuth(account_id_);
-  service->FinalizeUnlock(true);
+  easy_unlock_service_->AttemptAuth(account_id_);
+  easy_unlock_service_->FinalizeUnlock(true);
   EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
   EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().value());
-  service->UpdateSmartLockState(SmartLockState::kPhoneNotAuthenticated);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneNotAuthenticated);
   EXPECT_EQ(proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
             fake_lock_handler_->GetAuthType(account_id_));
   EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
   EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().value());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, IsSmartLockStateValidOnRemoteAuthFailure) {
+TEST_F(EasyUnlockServiceTest, IsSmartLockStateValidOnRemoteAuthFailure) {
   InitializeService(/*should_initialize_all_dependencies=*/true);
   SetScreenLockState(/*is_locked=*/true);
-  EasyUnlockService* service = easy_unlock_service_regular_.get();
   for (const SmartLockStateTestCase& testcase : kSmartLockStateTestCases) {
     fake_lock_handler_->ClearSmartLockState();
     fake_lock_handler_->ClearSmartLockAuthResult();
-    service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
-    service->AttemptAuth(account_id_);
-    service->UpdateSmartLockState(testcase.smart_lock_state);
+    easy_unlock_service_->UpdateSmartLockState(
+        SmartLockState::kPhoneAuthenticated);
+    easy_unlock_service_->AttemptAuth(account_id_);
+    easy_unlock_service_->UpdateSmartLockState(testcase.smart_lock_state);
     if (testcase.smart_lock_state != SmartLockState::kPhoneAuthenticated) {
       EXPECT_EQ(proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
                 fake_lock_handler_->GetAuthType(account_id_));
@@ -733,40 +716,38 @@
   }
 }
 
-TEST_F(EasyUnlockServiceRegularTest, FinalizeUnlock) {
+TEST_F(EasyUnlockServiceTest, FinalizeUnlock) {
   InitializeService(/*should_initialize_all_dependencies=*/true);
   SetScreenLockState(/*is_locked=*/true);
-  EasyUnlockService* service = easy_unlock_service_regular_.get();
-  service->FinalizeUnlock(true);
+  easy_unlock_service_->FinalizeUnlock(true);
   EXPECT_EQ(0, fake_lock_handler_->unlock_called());
-  service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
-  service->AttemptAuth(account_id_);
-  service->FinalizeUnlock(true);
+  easy_unlock_service_->UpdateSmartLockState(
+      SmartLockState::kPhoneAuthenticated);
+  easy_unlock_service_->AttemptAuth(account_id_);
+  easy_unlock_service_->FinalizeUnlock(true);
   EXPECT_EQ(1, fake_lock_handler_->unlock_called());
-  service->FinalizeUnlock(false);
+  easy_unlock_service_->FinalizeUnlock(false);
   EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
   EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().value());
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetPasswordAuthEvent) {
+TEST_F(EasyUnlockServiceTest, GetPasswordAuthEvent) {
   InitializeService(/*should_initialize_all_dependencies=*/true);
   SetScreenLockState(/*is_locked=*/true);
-  EasyUnlockService* service = easy_unlock_service_regular_.get();
   ResetSmartLockState();
   EXPECT_EQ(EasyUnlockAuthEvent::PASSWORD_ENTRY_NO_SMARTLOCK_STATE_HANDLER,
             GetPasswordAuthEvent());
   for (const SmartLockStateTestCase& testcase : kSmartLockStateTestCases) {
     fake_lock_handler_->ClearSmartLockState();
     fake_lock_handler_->ClearSmartLockAuthResult();
-    service->UpdateSmartLockState(testcase.smart_lock_state);
+    easy_unlock_service_->UpdateSmartLockState(testcase.smart_lock_state);
     EXPECT_EQ(testcase.easy_unlock_auth_event, GetPasswordAuthEvent());
   }
 }
 
-TEST_F(EasyUnlockServiceRegularTest, GetSmartUnlockPasswordAuthEvent) {
+TEST_F(EasyUnlockServiceTest, GetSmartUnlockPasswordAuthEvent) {
   InitializeService(/*should_initialize_all_dependencies=*/true);
   SetScreenLockState(/*is_locked=*/true);
-  EasyUnlockService* service = easy_unlock_service_regular_.get();
   ResetSmartLockState();
   EXPECT_EQ(
       SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::kUnknownState,
@@ -774,7 +755,7 @@
   for (const SmartLockStateTestCase& testcase : kSmartLockStateTestCases) {
     fake_lock_handler_->ClearSmartLockState();
     fake_lock_handler_->ClearSmartLockAuthResult();
-    service->UpdateSmartLockState(testcase.smart_lock_state);
+    easy_unlock_service_->UpdateSmartLockState(testcase.smart_lock_state);
     EXPECT_EQ(testcase.smart_lock_auth_event_password_state,
               GetSmartUnlockPasswordAuthEvent());
   }
diff --git a/chrome/browser/ash/login/easy_unlock/smartlock_state_handler.cc b/chrome/browser/ash/login/easy_unlock/smartlock_state_handler.cc
deleted file mode 100644
index 920cabd..0000000
--- a/chrome/browser/ash/login/easy_unlock/smartlock_state_handler.cc
+++ /dev/null
@@ -1,269 +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.
-
-#include "chrome/browser/ash/login/easy_unlock/smartlock_state_handler.h"
-
-#include <stddef.h>
-
-#include <string>
-
-#include "ash/constants/ash_features.h"
-#include "ash/public/cpp/smartlock_state.h"
-#include "base/functional/bind.h"
-#include "base/strings/utf_string_conversions.h"
-#include "build/build_config.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_metrics.h"
-#include "chrome/grit/generated_resources.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/chromeos/devicetype_utils.h"
-
-namespace ash {
-
-namespace {
-
-proximity_auth::ScreenlockBridge::UserPodCustomIcon GetIconForState(
-    SmartLockState state) {
-  switch (state) {
-    case SmartLockState::kPhoneFoundLockedAndProximate:
-      // The Smart Lock revamp UI needs to be able to distinguish the proximate
-      // case.
-      // TODO(crbug.com/1233614): Remove this special case once SmartLockState
-      // is routed directly to SmartLockAuthFactorModel.
-      if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
-        return proximity_auth::ScreenlockBridge::
-            USER_POD_CUSTOM_ICON_LOCKED_TO_BE_ACTIVATED;
-      }
-      [[fallthrough]];
-    case SmartLockState::kBluetoothDisabled:
-    case SmartLockState::kPhoneNotFound:
-    case SmartLockState::kPhoneNotAuthenticated:
-    case SmartLockState::kPhoneNotLockable:
-      return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED;
-    case SmartLockState::kPhoneFoundUnlockedAndDistant:
-    case SmartLockState::kPhoneFoundLockedAndDistant:
-      // TODO(isherman): This icon is currently identical to the regular locked
-      // icon.  Once the reduced proximity range flag is removed, consider
-      // deleting the redundant icon.
-      return proximity_auth::ScreenlockBridge::
-          USER_POD_CUSTOM_ICON_LOCKED_WITH_PROXIMITY_HINT;
-    case SmartLockState::kConnectingToPhone:
-      return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_SPINNER;
-    case SmartLockState::kPhoneAuthenticated:
-      return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_UNLOCKED;
-    case SmartLockState::kInactive:
-    case SmartLockState::kDisabled:
-      return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_NONE;
-    case SmartLockState::kPrimaryUserAbsent:
-      return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED;
-  }
-
-  NOTREACHED();
-  return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_NONE;
-}
-
-size_t GetTooltipResourceId(SmartLockState state) {
-  switch (state) {
-    case SmartLockState::kInactive:
-    case SmartLockState::kDisabled:
-    case SmartLockState::kConnectingToPhone:
-    case SmartLockState::kPhoneAuthenticated:
-      return 0;
-    case SmartLockState::kBluetoothDisabled:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_NO_BLUETOOTH;
-    case SmartLockState::kPhoneNotFound:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_NO_PHONE;
-    case SmartLockState::kPhoneNotAuthenticated:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_NOT_AUTHENTICATED;
-    case SmartLockState::kPhoneFoundLockedAndProximate:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_LOCKED;
-    case SmartLockState::kPhoneNotLockable:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_UNLOCKABLE;
-    case SmartLockState::kPhoneFoundUnlockedAndDistant:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_RSSI_TOO_LOW;
-    case SmartLockState::kPhoneFoundLockedAndDistant:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_LOCKED_AND_RSSI_TOO_LOW;
-    case SmartLockState::kPrimaryUserAbsent:
-      return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PRIMARY_USER_ABSENT;
-  }
-
-  NOTREACHED();
-  return 0;
-}
-
-bool TooltipContainsDeviceType(SmartLockState state) {
-  return (state == SmartLockState::kPhoneAuthenticated ||
-          state == SmartLockState::kPhoneNotLockable ||
-          state == SmartLockState::kBluetoothDisabled ||
-          state == SmartLockState::kPhoneFoundUnlockedAndDistant ||
-          state == SmartLockState::kPhoneFoundLockedAndDistant);
-}
-
-// Returns true iff the `state` corresponds to a locked remote device.
-bool IsLockedState(SmartLockState state) {
-  return (state == SmartLockState::kPhoneFoundLockedAndProximate ||
-          state == SmartLockState::kPhoneFoundLockedAndDistant);
-}
-
-}  // namespace
-
-SmartLockStateHandler::SmartLockStateHandler(
-    const AccountId& account_id,
-    proximity_auth::ScreenlockBridge* screenlock_bridge)
-    : state_(SmartLockState::kInactive),
-      account_id_(account_id),
-      screenlock_bridge_(screenlock_bridge) {
-  DCHECK(screenlock_bridge_);
-  screenlock_bridge_->AddObserver(this);
-}
-
-SmartLockStateHandler::~SmartLockStateHandler() {
-  screenlock_bridge_->RemoveObserver(this);
-  // Make sure the Smart Lock state set by this gets cleared.
-  ChangeState(SmartLockState::kInactive);
-}
-
-bool SmartLockStateHandler::IsActive() const {
-  return state_ != SmartLockState::kInactive;
-}
-
-bool SmartLockStateHandler::InStateValidOnRemoteAuthFailure() const {
-  // Note that NO_PHONE is not valid in this case because the phone may close
-  // the connection if the auth challenge sent to it is invalid. This case
-  // should be handled as authentication failure.
-  return state_ == SmartLockState::kInactive ||
-         state_ == SmartLockState::kDisabled ||
-         state_ == SmartLockState::kBluetoothDisabled ||
-         state_ == SmartLockState::kPhoneFoundLockedAndProximate;
-}
-
-void SmartLockStateHandler::ChangeState(SmartLockState new_state) {
-  if (state_ == new_state)
-    return;
-
-  state_ = new_state;
-
-  // If lock screen is not active or it forces offline password, just cache the
-  // current state. The screenlock state will get refreshed in `ScreenDidLock`.
-  if (!screenlock_bridge_->IsLocked())
-    return;
-
-  // Do nothing when auth type is online.
-  if (screenlock_bridge_->lock_handler()->GetAuthType(account_id_) ==
-      proximity_auth::mojom::AuthType::ONLINE_SIGN_IN) {
-    return;
-  }
-
-  if (IsLockedState(state_))
-    did_see_locked_phone_ = true;
-
-  UpdateScreenlockAuthType();
-
-  // TODO(crbug.com/1233614): Return early if kSmartLockUIRevamp is enabled.
-
-  proximity_auth::ScreenlockBridge::UserPodCustomIcon icon =
-      GetIconForState(state_);
-
-  if (icon == proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_NONE) {
-    screenlock_bridge_->lock_handler()->HideUserPodCustomIcon(account_id_);
-    return;
-  }
-
-  proximity_auth::ScreenlockBridge::UserPodCustomIconInfo icon_info;
-  icon_info.SetIcon(icon);
-
-  UpdateTooltipOptions(&icon_info);
-
-  // For states without tooltips, we still need to set an accessibility label.
-  if (state_ == SmartLockState::kConnectingToPhone) {
-    icon_info.SetAriaLabel(
-        l10n_util::GetStringUTF16(IDS_SMART_LOCK_SPINNER_ACCESSIBILITY_LABEL));
-  }
-
-  // Accessibility users may not be able to see the green icon which indicates
-  // the phone is authenticated. Provide message to that effect.
-  if (state_ == SmartLockState::kPhoneAuthenticated) {
-    icon_info.SetAriaLabel(l10n_util::GetStringUTF16(
-        IDS_SMART_LOCK_SCREENLOCK_AUTHENTICATED_LABEL));
-  }
-
-  screenlock_bridge_->lock_handler()->ShowUserPodCustomIcon(account_id_,
-                                                            icon_info);
-}
-
-void SmartLockStateHandler::OnScreenDidLock(
-    proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
-  did_see_locked_phone_ = IsLockedState(state_);
-  RefreshSmartLockState();
-}
-
-void SmartLockStateHandler::OnScreenDidUnlock(
-    proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
-  // Upon a successful unlock event, record whether the user's phone was locked
-  // at any point while the lock screen was up.
-  if (state_ == SmartLockState::kPhoneAuthenticated)
-    RecordEasyUnlockDidUserManuallyUnlockPhone(did_see_locked_phone_);
-  did_see_locked_phone_ = false;
-}
-
-void SmartLockStateHandler::OnFocusedUserChanged(const AccountId& account_id) {}
-
-void SmartLockStateHandler::RefreshSmartLockState() {
-  SmartLockState last_state = state_;
-  // This should force updating smart lock state.
-  state_ = SmartLockState::kInactive;
-  ChangeState(last_state);
-}
-
-void SmartLockStateHandler::UpdateTooltipOptions(
-    proximity_auth::ScreenlockBridge::UserPodCustomIconInfo* icon_info) {
-  size_t resource_id = 0;
-  std::u16string device_name;
-  resource_id = GetTooltipResourceId(state_);
-  if (TooltipContainsDeviceType(state_))
-    device_name = GetDeviceName();
-
-  if (!resource_id)
-    return;
-
-  std::u16string tooltip;
-  if (device_name.empty()) {
-    tooltip = l10n_util::GetStringUTF16(resource_id);
-  } else {
-    tooltip = l10n_util::GetStringFUTF16(resource_id, device_name);
-  }
-
-  if (tooltip.empty())
-    return;
-
-  bool autoshow_tooltip = state_ != SmartLockState::kPhoneAuthenticated;
-  icon_info->SetTooltip(tooltip, autoshow_tooltip);
-}
-
-std::u16string SmartLockStateHandler::GetDeviceName() {
-  return ui::GetChromeOSDeviceName();
-}
-
-void SmartLockStateHandler::UpdateScreenlockAuthType() {
-  // Do not override online signin.
-  const proximity_auth::mojom::AuthType existing_auth_type =
-      screenlock_bridge_->lock_handler()->GetAuthType(account_id_);
-  DCHECK_NE(proximity_auth::mojom::AuthType::ONLINE_SIGN_IN,
-            existing_auth_type);
-
-  if (state_ == SmartLockState::kPhoneAuthenticated) {
-    if (existing_auth_type != proximity_auth::mojom::AuthType::USER_CLICK) {
-      screenlock_bridge_->lock_handler()->SetAuthType(
-          account_id_, proximity_auth::mojom::AuthType::USER_CLICK,
-          l10n_util::GetStringUTF16(
-              IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE));
-    }
-  } else if (existing_auth_type !=
-             proximity_auth::mojom::AuthType::OFFLINE_PASSWORD) {
-    screenlock_bridge_->lock_handler()->SetAuthType(
-        account_id_, proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
-        std::u16string());
-  }
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/login/easy_unlock/smartlock_state_handler.h b/chrome/browser/ash/login/easy_unlock/smartlock_state_handler.h
deleted file mode 100644
index 27ea147..0000000
--- a/chrome/browser/ash/login/easy_unlock/smartlock_state_handler.h
+++ /dev/null
@@ -1,83 +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.
-
-#ifndef CHROME_BROWSER_ASH_LOGIN_EASY_UNLOCK_SMARTLOCK_STATE_HANDLER_H_
-#define CHROME_BROWSER_ASH_LOGIN_EASY_UNLOCK_SMARTLOCK_STATE_HANDLER_H_
-
-#include <string>
-
-#include "base/memory/raw_ptr.h"
-#include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
-#include "components/account_id/account_id.h"
-
-namespace ash {
-
-enum class SmartLockState;
-
-// Profile specific class responsible for updating screenlock UI for the user
-// associated with the profile when their Easy Unlock state changes.
-class SmartLockStateHandler
-    : public proximity_auth::ScreenlockBridge::Observer {
- public:
-  // `account_id`: The account id of the user associated with the profile to
-  //     which this class is attached.
-  // `screenlock_bridge`: The screenlock bridge used to update the Smart Lock
-  //     state.
-  SmartLockStateHandler(const AccountId& account_id,
-                        proximity_auth::ScreenlockBridge* screenlock_bridge);
-
-  SmartLockStateHandler(const SmartLockStateHandler&) = delete;
-  SmartLockStateHandler& operator=(const SmartLockStateHandler&) = delete;
-
-  ~SmartLockStateHandler() override;
-
-  // Returns true if handler is not in INACTIVE state.
-  bool IsActive() const;
-
-  // Whether the handler is in state that is allowed just after auth failure
-  // (i.e. the state that would cause auth failure rather than one caused by an
-  // auth failure).
-  bool InStateValidOnRemoteAuthFailure() const;
-
-  // Changes internal state to `new_state` and updates the user's Smart Lock
-  // state accordingly.
-  void ChangeState(SmartLockState new_state);
-
-  SmartLockState state() const { return state_; }
-
- private:
-  // proximity_auth::ScreenlockBridge::Observer:
-  void OnScreenDidLock(proximity_auth::ScreenlockBridge::LockHandler::ScreenType
-                           screen_type) override;
-  void OnScreenDidUnlock(
-      proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type)
-      override;
-  void OnFocusedUserChanged(const AccountId& account_id) override;
-
-  // Forces refresh of the Smart Lock UI.
-  void RefreshSmartLockState();
-
-  // Updates icon's tooltip options.
-  void UpdateTooltipOptions(
-      proximity_auth::ScreenlockBridge::UserPodCustomIconInfo* icon_info);
-
-  // Gets the name to be used for the device. The name depends on the device
-  // type (example values: Chromebook and Chromebox).
-  std::u16string GetDeviceName();
-
-  // Updates the screenlock auth type if it has to be changed.
-  void UpdateScreenlockAuthType();
-
-  SmartLockState state_;
-  const AccountId account_id_;
-  raw_ptr<proximity_auth::ScreenlockBridge, ExperimentalAsh>
-      screenlock_bridge_ = nullptr;
-
-  // Whether the user's phone was ever locked while on the current lock screen.
-  bool did_see_locked_phone_ = false;
-};
-
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_LOGIN_EASY_UNLOCK_SMARTLOCK_STATE_HANDLER_H_
diff --git a/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc b/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
index 40fc720..a2e1cfc 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/ash/login/enrollment/enrollment_screen_view.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/enrollment_ui_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_or_lock_screen_visible_waiter.h"
@@ -29,7 +28,6 @@
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ash/login/test/oobe_screens_utils.h"
-#include "chrome/browser/ash/login/test/policy_test_server_constants.h"
 #include "chrome/browser/ash/login/test/test_condition_waiter.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
@@ -39,6 +37,8 @@
 #include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
 #include "chrome/browser/ash/policy/enrollment/psm/rlwe_test_support.h"
 #include "chrome/browser/ash/policy/server_backed_state/server_backed_state_keys_broker.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
+#include "chrome/browser/ash/policy/test_support/policy_test_server_constants.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/ui/webui/ash/login/device_disabled_screen_handler.h"
diff --git a/chrome/browser/ash/login/existing_user_controller.cc b/chrome/browser/ash/login/existing_user_controller.cc
index 094abf4..0c3807c 100644
--- a/chrome/browser/ash/login/existing_user_controller.cc
+++ b/chrome/browser/ash/login/existing_user_controller.cc
@@ -58,7 +58,6 @@
 #include "chrome/browser/ash/login/ui/signin_ui.h"
 #include "chrome/browser/ash/login/ui/user_adding_screen.h"
 #include "chrome/browser/ash/login/ui/webui_login_view.h"
-#include "chrome/browser/ash/login/user_flow.h"
 #include "chrome/browser/ash/login/users/chrome_user_manager.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
@@ -742,12 +741,6 @@
 
   PerformLoginFinishedActions(false /* don't start auto login timer */);
 
-  if (ChromeUserManager::Get()
-          ->GetUserFlow(last_login_attempt_account_id_)
-          ->HandleLoginFailure(failure)) {
-    return;
-  }
-
   const bool is_known_user = user_manager::UserManager::Get()->IsKnownUser(
       last_login_attempt_account_id_);
   if (failure.reason() == AuthFailure::OWNER_REQUIRED) {
@@ -798,10 +791,6 @@
     StartAutoLoginTimer();
   }
 
-  // Reset user flow to default, so that special flow will not affect next
-  // attempt.
-  ChromeUserManager::Get()->ResetUserFlow(last_login_attempt_account_id_);
-
   for (auto& auth_status_consumer : auth_status_consumers_)
     auth_status_consumer.OnAuthFailure(failure);
 
@@ -819,10 +808,6 @@
   password_changed_ = login_performer_->password_changed();
   auth_mode_ = login_performer_->auth_mode();
 
-  ChromeUserManager::Get()
-      ->GetUserFlow(user_context.GetAccountId())
-      ->HandleLoginSuccess(user_context);
-
   StopAutoLoginTimer();
 
   // The hibernate service is not supported, just continue directly.
diff --git a/chrome/browser/ash/login/oobe_interactive_ui_test.cc b/chrome/browser/ash/login/oobe_interactive_ui_test.cc
index c9da6c88..fa9ae8b 100644
--- a/chrome/browser/ash/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/ash/login/oobe_interactive_ui_test.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_delegate.h"
 #include "chrome/browser/ash/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/embedded_test_server_setup_mixin.h"
 #include "chrome/browser/ash/login/test/enrollment_ui_mixin.h"
 #include "chrome/browser/ash/login/test/fake_arc_tos_mixin.h"
@@ -47,6 +46,7 @@
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.h"
 #include "chrome/browser/ash/policy/enrollment/psm/rlwe_test_support.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
 #include "chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.h"
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn b/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
index 017acb5..eb88892 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
@@ -53,6 +53,8 @@
     "//components/cbor",
   ]
   sources = [
+    "fake_connection.cc",
+    "fake_connection.h",
     "fake_target_device_connection_broker.cc",
     "fake_target_device_connection_broker.h",
     "fido_authentication_message_helper.cc",
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.cc
new file mode 100644
index 0000000..0b24311
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.cc
@@ -0,0 +1,50 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.h"
+
+namespace ash::quick_start {
+
+FakeConnection::Factory::Factory() = default;
+FakeConnection::Factory::~Factory() = default;
+
+std::unique_ptr<Connection> FakeConnection::Factory::Create(
+    NearbyConnection* nearby_connection,
+    Connection::SessionContext session_context,
+    mojo::SharedRemote<mojom::QuickStartDecoder> quick_start_decoder,
+    ConnectionClosedCallback on_connection_closed,
+    ConnectionAuthenticatedCallback on_connection_authenticated) {
+  auto connection = std::make_unique<FakeConnection>(
+      nearby_connection, session_context, std::move(quick_start_decoder),
+      std::move(on_connection_closed), std::move(on_connection_authenticated));
+  instance_ = connection->weak_ptr_factory_.GetWeakPtr();
+  return std::move(connection);
+}
+
+FakeConnection::FakeConnection(
+    NearbyConnection* nearby_connection,
+    Connection::SessionContext session_context,
+    mojo::SharedRemote<mojom::QuickStartDecoder> quick_start_decoder,
+    ConnectionClosedCallback on_connection_closed,
+    ConnectionAuthenticatedCallback on_connection_authenticated)
+    : Connection(nearby_connection,
+                 session_context,
+                 std::move(quick_start_decoder),
+                 std::make_unique<Connection::NonceGenerator>(),
+                 std::move(on_connection_closed),
+                 std::move(on_connection_authenticated)) {}
+
+FakeConnection::~FakeConnection() = default;
+
+void FakeConnection::InitiateHandshake(const std::string& authentication_token,
+                                       HandshakeSuccessCallback callback) {
+  handshake_initiated_ = true;
+  handshake_success_callback_ = std::move(callback);
+}
+
+bool FakeConnection::WasHandshakeInitiated() {
+  return handshake_initiated_;
+}
+
+}  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.h
new file mode 100644
index 0000000..bd3341e
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.h
@@ -0,0 +1,53 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_FAKE_CONNECTION_H_
+#define CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_FAKE_CONNECTION_H_
+
+#include "chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h"
+
+namespace ash::quick_start {
+
+class FakeConnection : public Connection {
+ public:
+  class Factory : public Connection::Factory {
+   public:
+    Factory();
+    ~Factory() override;
+
+    // Connection::Factory:
+    std::unique_ptr<Connection> Create(
+        NearbyConnection* nearby_connection,
+        Connection::SessionContext session_context,
+        mojo::SharedRemote<mojom::QuickStartDecoder> quick_start_decoder,
+        ConnectionClosedCallback on_connection_closed,
+        ConnectionAuthenticatedCallback on_connection_authenticated) override;
+
+    base::WeakPtr<FakeConnection> instance_;
+  };
+
+  FakeConnection(
+      NearbyConnection* nearby_connection,
+      Connection::SessionContext session_context,
+      mojo::SharedRemote<mojom::QuickStartDecoder> quick_start_decoder,
+      ConnectionClosedCallback on_connection_closed,
+      ConnectionAuthenticatedCallback on_connection_authenticated);
+
+  ~FakeConnection() override;
+
+  // Connection:
+  void InitiateHandshake(const std::string& authentication_token,
+                         HandshakeSuccessCallback callback) override;
+
+  bool WasHandshakeInitiated();
+
+ private:
+  bool handshake_initiated_ = false;
+  HandshakeSuccessCallback handshake_success_callback_;
+
+  base::WeakPtrFactory<FakeConnection> weak_ptr_factory_{this};
+};
+}  // namespace ash::quick_start
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_FAKE_CONNECTION_H_
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
index 430f7003..dbe5744 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/task_environment.h"
+#include "chrome/browser/ash/login/oobe_quick_start/connectivity/fake_connection.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/fast_pair_advertiser.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/random_session_id.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
@@ -286,54 +287,6 @@
       connection_closed_reason_;
 };
 
-class FakeConnection : public Connection {
- public:
-  class Factory : public Connection::Factory {
-   public:
-    // Connection::Factory:
-    std::unique_ptr<Connection> Create(
-        NearbyConnection* nearby_connection,
-        Connection::SessionContext session_context,
-        mojo::SharedRemote<mojom::QuickStartDecoder> quick_start_decoder,
-        ConnectionClosedCallback on_connection_closed,
-        ConnectionAuthenticatedCallback on_connection_authenticated) override {
-      auto connection = std::make_unique<FakeConnection>(
-          nearby_connection, session_context, std::move(quick_start_decoder),
-          std::move(on_connection_closed),
-          std::move(on_connection_authenticated));
-      instance_ = connection->weak_ptr_factory_.GetWeakPtr();
-      return std::move(connection);
-    }
-
-    base::WeakPtr<FakeConnection> instance_;
-  };
-
-  FakeConnection(
-      NearbyConnection* nearby_connection,
-      Connection::SessionContext session_context,
-      mojo::SharedRemote<mojom::QuickStartDecoder> quick_start_decoder,
-      ConnectionClosedCallback on_connection_closed,
-      ConnectionAuthenticatedCallback on_connection_authenticated)
-      : Connection(nearby_connection,
-                   session_context,
-                   std::move(quick_start_decoder),
-                   std::make_unique<Connection::NonceGenerator>(),
-                   std::move(on_connection_closed),
-                   std::move(on_connection_authenticated)) {}
-
-  // Connection:
-  void InitiateHandshake(const std::string& authentication_token,
-                         HandshakeSuccessCallback callback) override {
-    handshake_initiated_ = true;
-    handshake_success_callback_ = std::move(callback);
-  }
-
-  bool handshake_initiated_ = false;
-  HandshakeSuccessCallback handshake_success_callback_;
-
-  base::WeakPtrFactory<FakeConnection> weak_ptr_factory_{this};
-};
-
 }  // namespace
 
 class TargetDeviceConnectionBrokerImplTest : public testing::Test {
@@ -820,7 +773,7 @@
       kEndpointId, std::vector<uint8_t>(), &fake_nearby_connection_);
 
   ASSERT_TRUE(connection());
-  EXPECT_TRUE(connection()->handshake_initiated_);
+  EXPECT_TRUE(connection()->WasHandshakeInitiated());
 }
 
 TEST_F(TargetDeviceConnectionBrokerImplTest,
diff --git a/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc b/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
index 45059fa..da67a8b 100644
--- a/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
+++ b/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
@@ -190,8 +190,7 @@
 }
 
 void PasswordSyncTokenFetcher::FetchSyncToken(const std::string& access_token) {
-  base::Value request_data(base::Value::Type::DICT);
-  request_data.SetStringKey(kTokenTypeKey, kTokenTypeValue);
+  auto request_data = base::Value::Dict().Set(kTokenTypeKey, kTokenTypeValue);
   std::string request_string;
   if (!base::JSONWriter::Write(request_data, &request_string)) {
     LOG(ERROR) << "Not able to serialize token request body.";
diff --git a/chrome/browser/ash/login/saml/saml_browsertest.cc b/chrome/browser/ash/login/saml/saml_browsertest.cc
index a6a4ab4..639fb5b 100644
--- a/chrome/browser/ash/login/saml/saml_browsertest.cc
+++ b/chrome/browser/ash/login/saml/saml_browsertest.cc
@@ -37,7 +37,6 @@
 #include "chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/enrollment_ui_mixin.h"
 #include "chrome/browser/ash/login/test/feature_parameter_interface.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
@@ -52,6 +51,7 @@
 #include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
diff --git a/chrome/browser/ash/login/screens/consolidated_consent_screen.cc b/chrome/browser/ash/login/screens/consolidated_consent_screen.cc
index 0f0e4bf..aa817d1 100644
--- a/chrome/browser/ash/login/screens/consolidated_consent_screen.cc
+++ b/chrome/browser/ash/login/screens/consolidated_consent_screen.cc
@@ -98,8 +98,9 @@
 
   if (recovery_setup.ask_about_recovery_consent) {
     // The user was shown the opt-in checkbox.
-    if (recovery_setup.recovery_factor_opted_in)
+    if (recovery_setup.recovery_factor_opted_in) {
       return ConsolidatedConsentScreen::RecoveryOptInResult::kUserOptIn;
+    }
     return ConsolidatedConsentScreen::RecoveryOptInResult::kUserOptOut;
   }
 
@@ -265,8 +266,9 @@
 void ConsolidatedConsentScreen::OnBackupAndRestoreModeChanged(bool enabled,
                                                               bool managed) {
   backup_restore_managed_ = managed;
-  if (view_)
+  if (view_) {
     view_->SetBackupMode(enabled, managed);
+  }
 }
 
 void ConsolidatedConsentScreen::OnLocationServicesModeChanged(bool enabled,
@@ -279,8 +281,9 @@
 void ConsolidatedConsentScreen::UpdateMetricsMode(bool enabled, bool managed) {
   // When the usage opt-in is not managed, override the enabled value
   // with `true` to encourage users to consent with it during OptIn flow.
-  if (view_)
+  if (view_) {
     view_->SetUsageMode(/*enabled=*/!managed || enabled, managed);
+  }
 }
 
 void ConsolidatedConsentScreen::OnOwnershipStatusCheckDone(
@@ -323,7 +326,7 @@
     arc::SetArcPlayStoreEnabledForProfile(profile, true);
 
     pref_handler_ = std::make_unique<arc::ArcOptInPreferenceHandler>(
-        this, profile->GetPrefs());
+        this, profile->GetPrefs(), g_browser_process->metrics_service());
     pref_handler_->Start();
   } else if (!is_demo) {
     // Since ARC OOBE Negotiation is not needed, we should avoid using
@@ -437,8 +440,9 @@
 
   if (arc::IsArcDemoModeSetupFlow() ||
       !arc::IsArcTermsOfServiceOobeNegotiationNeeded()) {
-    for (auto& observer : observer_list_)
+    for (auto& observer : observer_list_) {
       observer.OnConsolidatedConsentAccept();
+    }
 
     ExitScreenWithAcceptedResult();
     return;
diff --git a/chrome/browser/ash/login/screens/edu_coexistence_login_browsertest.cc b/chrome/browser/ash/login/screens/edu_coexistence_login_browsertest.cc
index 3a50cd2..aaaaafdc 100644
--- a/chrome/browser/ash/login/screens/edu_coexistence_login_browsertest.cc
+++ b/chrome/browser/ash/login/screens/edu_coexistence_login_browsertest.cc
@@ -7,7 +7,6 @@
 #include "base/functional/callback.h"
 #include "base/run_loop.h"
 #include "base/test/test_future.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
 #include "chrome/browser/ash/login/test/oobe_screen_exit_waiter.h"
@@ -17,6 +16,7 @@
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ui/webui/ash/login/locale_switch_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/ash/login/user_creation_screen_handler.h"
diff --git a/chrome/browser/ash/login/screens/family_link_notice_browsertest.cc b/chrome/browser/ash/login/screens/family_link_notice_browsertest.cc
index 806c24c5..00f89fcf 100644
--- a/chrome/browser/ash/login/screens/family_link_notice_browsertest.cc
+++ b/chrome/browser/ash/login/screens/family_link_notice_browsertest.cc
@@ -5,7 +5,6 @@
 
 #include "ash/constants/ash_features.h"
 #include "chrome/browser/ash/login/oobe_screen.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
@@ -14,6 +13,7 @@
 #include "chrome/browser/ash/login/test/wizard_controller_screen_exit_waiter.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/user_creation_screen_handler.h"
diff --git a/chrome/browser/ash/login/screens/marketing_opt_in_screen_browsertest.cc b/chrome/browser/ash/login/screens/marketing_opt_in_screen_browsertest.cc
index 2b1e99c..656c359 100644
--- a/chrome/browser/ash/login/screens/marketing_opt_in_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/marketing_opt_in_screen_browsertest.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chrome/browser/ash/login/login_pref_names.h"
 #include "chrome/browser/ash/login/marketing_backend_connector.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/local_state_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
@@ -29,6 +28,7 @@
 #include "chrome/browser/ash/login/test/user_policy_mixin.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser.h"
diff --git a/chrome/browser/ash/login/screens/parental_handoff_screen_browsertest.cc b/chrome/browser/ash/login/screens/parental_handoff_screen_browsertest.cc
index 80cd11c0c..d66339e 100644
--- a/chrome/browser/ash/login/screens/parental_handoff_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/parental_handoff_screen_browsertest.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/ash/child_accounts/family_features.h"
 #include "chrome/browser/ash/login/screens/assistant_optin_flow_screen.h"
 #include "chrome/browser/ash/login/screens/edu_coexistence_login_screen.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
@@ -25,6 +24,7 @@
 #include "chrome/browser/ash/login/test/wizard_controller_screen_exit_waiter.h"
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/ui/webui/ash/login/assistant_optin_flow_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/parental_handoff_screen_handler.h"
diff --git a/chrome/browser/ash/login/screens/terms_of_service_screen_browsertest.cc b/chrome/browser/ash/login/screens/terms_of_service_screen_browsertest.cc
index 3f1d151..b5325ce 100644
--- a/chrome/browser/ash/login/screens/terms_of_service_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/terms_of_service_screen_browsertest.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
 #include "chrome/browser/ash/login/test/cryptohome_mixin.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/embedded_test_server_setup_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
@@ -29,6 +28,7 @@
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
diff --git a/chrome/browser/ash/login/session_login_browsertest.cc b/chrome/browser/ash/login/session_login_browsertest.cc
index 6b2f2c2..5100eed 100644
--- a/chrome/browser/ash/login/session_login_browsertest.cc
+++ b/chrome/browser/ash/login/session_login_browsertest.cc
@@ -13,14 +13,13 @@
 #include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_screen_exit_waiter.h"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/test/user_policy_mixin.h"
-#include "chrome/browser/ash/login/user_flow.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser.h"
diff --git a/chrome/browser/ash/login/signin/auth_error_observer.cc b/chrome/browser/ash/login/signin/auth_error_observer.cc
index 3569b627..0eac83b 100644
--- a/chrome/browser/ash/login/signin/auth_error_observer.cc
+++ b/chrome/browser/ash/login/signin/auth_error_observer.cc
@@ -79,9 +79,11 @@
       ProfileHelper::Get()->GetUserByProfile(profile_);
   DCHECK(user->HasGaiaAccount());
 
-  if (auth_error.IsPersistentError()) {
+  if (auth_error.IsPersistentError() && !auth_error.IsScopePersistentError()) {
     // Invalidate OAuth2 refresh token to force Gaia sign-in flow. This is
-    // needed because sign-out/sign-in solution is suggested to the user.
+    // needed because sign-out/sign-in solution is suggested to the user. Do
+    // this only for persistent errors which are not caused because of a service
+    // requesting an invalid scope.
     LOG(WARNING) << "Invalidate OAuth token because of an auth error: "
                  << auth_error.ToString();
     const AccountId& account_id = user->GetAccountId();
diff --git a/chrome/browser/ash/login/test/js_checker.cc b/chrome/browser/ash/login/test/js_checker.cc
index 1697eabb5..a753b8a 100644
--- a/chrome/browser/ash/login/test/js_checker.cc
+++ b/chrome/browser/ash/login/test/js_checker.cc
@@ -86,7 +86,7 @@
 
 void JSChecker::Evaluate(const std::string& expression) {
   CHECK(web_contents_);
-  ASSERT_TRUE(content::ExecuteScript(web_contents_.get(), expression));
+  ASSERT_TRUE(content::ExecJs(web_contents_.get(), expression));
 }
 
 void JSChecker::ExecuteAsync(const std::string& expression) {
@@ -544,7 +544,7 @@
 }
 
 void ExecuteOobeJS(const std::string& script) {
-  ASSERT_TRUE(content::ExecuteScript(
+  ASSERT_TRUE(content::ExecJs(
       LoginDisplayHost::default_host()->GetOobeWebContents(), script));
 }
 
diff --git a/chrome/browser/ash/login/test/logged_in_user_mixin.h b/chrome/browser/ash/login/test/logged_in_user_mixin.h
index 4a7852b..79b87c4 100644
--- a/chrome/browser/ash/login/test/logged_in_user_mixin.h
+++ b/chrome/browser/ash/login/test/logged_in_user_mixin.h
@@ -6,11 +6,11 @@
 #define CHROME_BROWSER_ASH_LOGIN_TEST_LOGGED_IN_USER_MIXIN_H_
 
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/embedded_test_server_setup_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/user_policy_mixin.h"
 #include "chrome/browser/ash/policy/core/user_policy_test_helper.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/test/base/fake_gaia_mixin.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/ash/login/test/policy_test_server_constants.h b/chrome/browser/ash/login/test/policy_test_server_constants.h
deleted file mode 100644
index a242102c..0000000
--- a/chrome/browser/ash/login/test/policy_test_server_constants.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ASH_LOGIN_TEST_POLICY_TEST_SERVER_CONSTANTS_H_
-#define CHROME_BROWSER_ASH_LOGIN_TEST_POLICY_TEST_SERVER_CONSTANTS_H_
-
-namespace ash {
-namespace test {
-
-// Test constants used during enrollment wherever appropriate.
-constexpr char kTestDomain[] = "test-domain.com";
-constexpr char kTestRlzBrandCodeKey[] = "TEST";
-constexpr char kTestSerialNumber[] = "111111";
-constexpr char kTestHardwareClass[] = "hw";
-
-}  // namespace test
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_LOGIN_TEST_POLICY_TEST_SERVER_CONSTANTS_H_
diff --git a/chrome/browser/ash/login/test/user_policy_mixin.cc b/chrome/browser/ash/login/test/user_policy_mixin.cc
index 34d3cbc..703d152 100644
--- a/chrome/browser/ash/login/test/user_policy_mixin.cc
+++ b/chrome/browser/ash/login/test/user_policy_mixin.cc
@@ -12,7 +12,7 @@
 #include "base/functional/bind.h"
 #include "base/path_service.h"
 #include "base/threading/thread_restrictions.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/common/chrome_paths.h"
 #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
 #include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
diff --git a/chrome/browser/ash/login/ui/login_display_host_webui.cc b/chrome/browser/ash/login/ui/login_display_host_webui.cc
index 917073a..c204aaf 100644
--- a/chrome/browser/ash/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/ash/login/ui/login_display_host_webui.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/accessibility/ui/focus_ring_controller.h"
+#include "ash/booting/booting_animation_controller.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/locale_update_controller.h"
@@ -561,6 +562,20 @@
     NotifyWizardCreated();
     wizard_controller_->Init(first_screen);
   }
+
+  if (ash::features::IsOobeSimonEnabled()) {
+    auto* welcome_screen = GetWizardController()->GetScreen<WelcomeScreen>();
+    const bool should_show =
+        wizard_controller_->current_screen() == welcome_screen;
+    if (!should_show) {
+      return;
+    }
+    ash::Shell::Get()
+        ->booting_animation_controller()
+        ->ShowAnimationWithEndCallback(
+            base::BindOnce(&LoginDisplayHostWebUI::BootingAnimationFinished,
+                           weak_factory_.GetWeakPtr()));
+  }
 }
 
 WizardController* LoginDisplayHostWebUI::GetWizardController() {
@@ -699,6 +714,15 @@
   ShowWebUI();
 }
 
+void LoginDisplayHostWebUI::BootingAnimationFinished() {
+  CHECK(features::IsOobeSimonEnabled());
+  content::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &BootingAnimationController::Finish,
+          ash::Shell::Get()->booting_animation_controller()->GetWeakPtr()));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // LoginDisplayHostWebUI, ui::InputDeviceEventObserver
 void LoginDisplayHostWebUI::OnInputDeviceConfigurationChanged(
@@ -746,7 +770,7 @@
     LOG(WARNING) << "LoginDisplayHostWebUI::OnCurrentScreenChanged() "
                     "NotifyLoginOrLockScreenVisible";
 
-    // First screen shown.
+    // Notify that the OOBE page is ready and the first screen is shown.
     session_manager::SessionManager::Get()->NotifyLoginOrLockScreenVisible();
   } else {
     // TODO(crbug.com/1305245) - Remove once the issue is fixed.
diff --git a/chrome/browser/ash/login/ui/login_display_host_webui.h b/chrome/browser/ash/login/ui/login_display_host_webui.h
index 2538281..02f13e56 100644
--- a/chrome/browser/ash/login/ui/login_display_host_webui.h
+++ b/chrome/browser/ash/login/ui/login_display_host_webui.h
@@ -211,6 +211,10 @@
   // Show OOBE WebUI if signal from javascript side never came.
   void OnShowWebUITimeout();
 
+  // Callback that is called once booting animation has finished running, but
+  // the last frame is still shown.
+  void BootingAnimationFinished();
+
   // Sign in screen controller.
   std::unique_ptr<ExistingUserController> existing_user_controller_;
 
diff --git a/chrome/browser/ash/login/ui/login_display_mojo.cc b/chrome/browser/ash/login/ui/login_display_mojo.cc
index b0ab4e93..80c210e 100644
--- a/chrome/browser/ash/login/ui/login_display_mojo.cc
+++ b/chrome/browser/ash/login/ui/login_display_mojo.cc
@@ -4,25 +4,10 @@
 
 #include "chrome/browser/ash/login/ui/login_display_mojo.h"
 
-#include "ash/public/cpp/login_screen.h"
-#include "ash/public/cpp/login_screen_model.h"
-#include "chrome/browser/ash/login/screens/user_selection_screen.h"
-#include "chrome/browser/ui/ash/login_screen_client_impl.h"
-
 namespace ash {
 
-LoginDisplayMojo::LoginDisplayMojo() {
-  user_manager::UserManager::Get()->AddObserver(this);
-}
+LoginDisplayMojo::LoginDisplayMojo() = default;
 
-LoginDisplayMojo::~LoginDisplayMojo() {
-  user_manager::UserManager::Get()->RemoveObserver(this);
-}
-
-void LoginDisplayMojo::OnUserImageChanged(const user_manager::User& user) {
-  LoginScreen::Get()->GetModel()->SetAvatarForUser(
-      user.GetAccountId(),
-      UserSelectionScreen::BuildAshUserAvatarForUser(user));
-}
+LoginDisplayMojo::~LoginDisplayMojo() = default;
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/ui/login_display_mojo.h b/chrome/browser/ash/login/ui/login_display_mojo.h
index 0dccc0a7..c8b872e 100644
--- a/chrome/browser/ash/login/ui/login_display_mojo.h
+++ b/chrome/browser/ash/login/ui/login_display_mojo.h
@@ -6,15 +6,13 @@
 #define CHROME_BROWSER_ASH_LOGIN_UI_LOGIN_DISPLAY_MOJO_H_
 
 #include "chrome/browser/ash/login/ui/login_display.h"
-#include "components/user_manager/user_manager.h"
 
 namespace ash {
 
 // Interface used by UI-agnostic code to send messages to views-based login
 // screen.
 // TODO(estade): rename to LoginDisplayAsh.
-class LoginDisplayMojo : public LoginDisplay,
-                         public user_manager::UserManager::Observer {
+class LoginDisplayMojo : public LoginDisplay {
  public:
   LoginDisplayMojo();
 
@@ -22,9 +20,6 @@
   LoginDisplayMojo& operator=(const LoginDisplayMojo&) = delete;
 
   ~LoginDisplayMojo() override;
-
-  // user_manager::UserManager::Observer:
-  void OnUserImageChanged(const user_manager::User& user) override;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/user_flow.cc b/chrome/browser/ash/login/user_flow.cc
deleted file mode 100644
index 08eb3b8..0000000
--- a/chrome/browser/ash/login/user_flow.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ash/login/user_flow.h"
-
-#include "base/functional/bind.h"
-#include "base/location.h"
-#include "base/task/single_thread_task_runner.h"
-#include "chrome/browser/ash/login/users/chrome_user_manager.h"
-#include "components/account_id/account_id.h"
-
-namespace ash {
-
-UserFlow::UserFlow() {}
-
-UserFlow::~UserFlow() {}
-
-DefaultUserFlow::~DefaultUserFlow() {}
-
-bool DefaultUserFlow::HandleLoginFailure(const AuthFailure& failure) {
-  return false;
-}
-
-void DefaultUserFlow::HandleLoginSuccess(const UserContext& context) {}
-
-ExtendedUserFlow::ExtendedUserFlow(const AccountId& account_id)
-    : account_id_(account_id) {}
-
-ExtendedUserFlow::~ExtendedUserFlow() {}
-
-void ExtendedUserFlow::UnregisterFlowSoon() {
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ChromeUserManager::ResetUserFlow,
-                     base::Unretained(ChromeUserManager::Get()), account_id()));
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/login/user_flow.h b/chrome/browser/ash/login/user_flow.h
deleted file mode 100644
index c22e33a..0000000
--- a/chrome/browser/ash/login/user_flow.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ASH_LOGIN_USER_FLOW_H_
-#define CHROME_BROWSER_ASH_LOGIN_USER_FLOW_H_
-
-#include "chromeos/ash/components/login/auth/auth_status_consumer.h"
-#include "components/account_id/account_id.h"
-#include "components/user_manager/user.h"
-
-namespace ash {
-
-class UserContext;
-
-// Defines possible variants of user flow upon logging in.
-// See UserManager::SetUserFlow for usage contract.
-class UserFlow {
- public:
-  UserFlow();
-  virtual ~UserFlow() = 0;
-
-  virtual bool HandleLoginFailure(const AuthFailure& failure) = 0;
-  virtual void HandleLoginSuccess(const UserContext& context) = 0;
-};
-
-// UserFlow implementation for regular login flow.
-class DefaultUserFlow : public UserFlow {
- public:
-  ~DefaultUserFlow() override;
-
-  // UserFlow:
-  bool HandleLoginFailure(const AuthFailure& failure) override;
-  void HandleLoginSuccess(const UserContext& context) override;
-};
-
-// UserFlow stub for non-regular flows.
-// TODO(b/227674947): ExtendedUserFlow can be removed and the UserFlow hierarchy
-// can be consolidated now that sign in with Smart Lock is deprecated. The only
-// non-standard user login flow, EasyUnlockUserLoginFlow, no longer exists.
-class ExtendedUserFlow : public UserFlow {
- public:
-  explicit ExtendedUserFlow(const AccountId& account_id);
-  ~ExtendedUserFlow() override;
-
- protected:
-  // Subclasses can call this method to unregister flow in the next event.
-  virtual void UnregisterFlowSoon();
-  const AccountId& account_id() { return account_id_; }
-
- private:
-  const AccountId account_id_;
-};
-
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_LOGIN_USER_FLOW_H_
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
index 47ab097..2dd6aef 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
@@ -1081,38 +1081,6 @@
   }
 }
 
-UserFlow* ChromeUserManagerImpl::GetCurrentUserFlow() const {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (!IsUserLoggedIn())
-    return GetDefaultUserFlow();
-  return GetUserFlow(GetActiveUser()->GetAccountId());
-}
-
-UserFlow* ChromeUserManagerImpl::GetUserFlow(
-    const AccountId& account_id) const {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  FlowMap::const_iterator it = specific_flows_.find(account_id);
-  if (it != specific_flows_.end())
-    return it->second;
-  return GetDefaultUserFlow();
-}
-
-void ChromeUserManagerImpl::SetUserFlow(const AccountId& account_id,
-                                        UserFlow* flow) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  ResetUserFlow(account_id);
-  specific_flows_[account_id] = flow;
-}
-
-void ChromeUserManagerImpl::ResetUserFlow(const AccountId& account_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  FlowMap::iterator it = specific_flows_.find(account_id);
-  if (it != specific_flows_.end()) {
-    delete it->second;
-    specific_flows_.erase(it);
-  }
-}
-
 bool ChromeUserManagerImpl::IsGuestSessionAllowed() const {
   // In tests CrosSettings might not be initialized.
   if (!cros_settings_)
@@ -1174,13 +1142,6 @@
       user.HasGaiaAccount() && IsGaiaUserAllowed(user));
 }
 
-UserFlow* ChromeUserManagerImpl::GetDefaultUserFlow() const {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (!default_flow_.get())
-    default_flow_ = std::make_unique<DefaultUserFlow>();
-  return default_flow_.get();
-}
-
 void ChromeUserManagerImpl::NotifyUserAddedToSession(
     const user_manager::User* added_user,
     bool user_switch_pending) {
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_impl.h b/chrome/browser/ash/login/users/chrome_user_manager_impl.h
index 8204dbc..bae3e14 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_impl.h
+++ b/chrome/browser/ash/login/users/chrome_user_manager_impl.h
@@ -16,7 +16,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/synchronization/lock.h"
-#include "chrome/browser/ash/login/user_flow.h"
 #include "chrome/browser/ash/login/users/affiliation.h"
 #include "chrome/browser/ash/login/users/avatar/user_image_manager_registry.h"
 #include "chrome/browser/ash/login/users/chrome_user_manager.h"
@@ -75,10 +74,6 @@
   MultiProfileUserController* GetMultiProfileUserController() override;
   UserImageManager* GetUserImageManager(const AccountId& account_id) override;
   SupervisedUserManager* GetSupervisedUserManager() override;
-  UserFlow* GetCurrentUserFlow() const override;
-  UserFlow* GetUserFlow(const AccountId& account_id) const override;
-  void SetUserFlow(const AccountId& account_id, UserFlow* flow) override;
-  void ResetUserFlow(const AccountId& account_id) override;
 
   // UserManager implementation:
   void Shutdown() override;
@@ -199,9 +194,6 @@
   // associated with that username.
   void UpdatePublicAccountDisplayName(const std::string& user_id);
 
-  // Lazily creates default user flow.
-  UserFlow* GetDefaultUserFlow() const;
-
   // MultiProfileUserControllerDelegate implementation:
   void OnUserNotAllowed(const std::string& user_email) override;
 
@@ -248,15 +240,6 @@
   // Session length limiter.
   std::unique_ptr<SessionLengthLimiter> session_length_limiter_;
 
-  using FlowMap = std::map<AccountId, UserFlow*>;
-
-  // Lazy-initialized default flow.
-  mutable std::unique_ptr<UserFlow> default_flow_;
-
-  // Specific flows by user e-mail. Keys should be canonicalized before
-  // access.
-  FlowMap specific_flows_;
-
   // Cros settings change subscriptions.
   base::CallbackListSubscription allow_guest_subscription_;
   base::CallbackListSubscription users_subscription_;
diff --git a/chrome/browser/ash/login/users/fake_chrome_user_manager.cc b/chrome/browser/ash/login/users/fake_chrome_user_manager.cc
index 10b1dc2..ff64c86 100644
--- a/chrome/browser/ash/login/users/fake_chrome_user_manager.cc
+++ b/chrome/browser/ash/login/users/fake_chrome_user_manager.cc
@@ -243,34 +243,6 @@
   return mgr_raw;
 }
 
-void FakeChromeUserManager::SetUserFlow(const AccountId& account_id,
-                                        UserFlow* flow) {
-  ResetUserFlow(account_id);
-  specific_flows_[account_id] = flow;
-}
-
-UserFlow* FakeChromeUserManager::GetCurrentUserFlow() const {
-  if (!IsUserLoggedIn())
-    return GetDefaultUserFlow();
-  return GetUserFlow(GetActiveUser()->GetAccountId());
-}
-
-UserFlow* FakeChromeUserManager::GetUserFlow(
-    const AccountId& account_id) const {
-  FlowMap::const_iterator it = specific_flows_.find(account_id);
-  if (it != specific_flows_.end())
-    return it->second;
-  return GetDefaultUserFlow();
-}
-
-void FakeChromeUserManager::ResetUserFlow(const AccountId& account_id) {
-  FlowMap::iterator it = specific_flows_.find(account_id);
-  if (it != specific_flows_.end()) {
-    delete it->second;
-    specific_flows_.erase(it);
-  }
-}
-
 void FakeChromeUserManager::SwitchActiveUser(const AccountId& account_id) {
   active_account_id_ = account_id;
   active_user_ = nullptr;
@@ -336,12 +308,6 @@
   return result;
 }
 
-UserFlow* FakeChromeUserManager::GetDefaultUserFlow() const {
-  if (!default_flow_.get())
-    default_flow_ = std::make_unique<DefaultUserFlow>();
-  return default_flow_.get();
-}
-
 void FakeChromeUserManager::SetOwnerId(const AccountId& account_id) {
   UserManagerBase::SetOwnerId(account_id);
 }
diff --git a/chrome/browser/ash/login/users/fake_chrome_user_manager.h b/chrome/browser/ash/login/users/fake_chrome_user_manager.h
index 43975338..c4a3f5e 100644
--- a/chrome/browser/ash/login/users/fake_chrome_user_manager.h
+++ b/chrome/browser/ash/login/users/fake_chrome_user_manager.h
@@ -13,7 +13,6 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/ash/login/user_flow.h"
 #include "chrome/browser/ash/login/users/chrome_user_manager.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/prefs/testing_pref_service.h"
@@ -163,10 +162,6 @@
   MultiProfileUserController* GetMultiProfileUserController() override;
   UserImageManager* GetUserImageManager(const AccountId& account_id) override;
   SupervisedUserManager* GetSupervisedUserManager() override;
-  void SetUserFlow(const AccountId& account_id, UserFlow* flow) override;
-  UserFlow* GetCurrentUserFlow() const override;
-  UserFlow* GetUserFlow(const AccountId& account_id) const override;
-  void ResetUserFlow(const AccountId& account_id) override;
 
   // ChromeUserManager override.
   void SetUserAffiliation(
@@ -216,9 +211,6 @@
   using UserImageManagerMap =
       std::map<AccountId, std::unique_ptr<UserImageManager>>;
 
-  // Lazily creates default user flow.
-  UserFlow* GetDefaultUserFlow() const;
-
   // Returns the active user.
   user_manager::User* GetActiveUserInternal() const;
 
@@ -237,15 +229,6 @@
 
   AccountId last_session_active_account_id_ = EmptyAccountId();
 
-  // Lazy-initialized default flow.
-  mutable std::unique_ptr<UserFlow> default_flow_;
-
-  using FlowMap = std::map<AccountId, UserFlow*>;
-
-  // Specific flows by user e-mail.
-  // Keys should be canonicalized before access.
-  FlowMap specific_flows_;
-
   // Whether the device is enterprise managed.
   bool is_enterprise_managed_ = false;
 
diff --git a/chrome/browser/ash/login/users/user_manager_interface.h b/chrome/browser/ash/login/users/user_manager_interface.h
index 1e513686..277a120 100644
--- a/chrome/browser/ash/login/users/user_manager_interface.h
+++ b/chrome/browser/ash/login/users/user_manager_interface.h
@@ -5,9 +5,6 @@
 #ifndef CHROME_BROWSER_ASH_LOGIN_USERS_USER_MANAGER_INTERFACE_H_
 #define CHROME_BROWSER_ASH_LOGIN_USERS_USER_MANAGER_INTERFACE_H_
 
-#include "components/user_manager/user.h"
-#include "components/user_manager/user_type.h"
-
 class AccountId;
 
 namespace ash {
@@ -15,41 +12,21 @@
 class MultiProfileUserController;
 class SupervisedUserManager;
 class UserImageManager;
-class UserFlow;
 
 // ChromeOS specific add-ons interface for the UserManager.
 class UserManagerInterface {
  public:
-  UserManagerInterface() {}
+  UserManagerInterface() = default;
 
   UserManagerInterface(const UserManagerInterface&) = delete;
   UserManagerInterface& operator=(const UserManagerInterface&) = delete;
 
-  virtual ~UserManagerInterface() {}
+  virtual ~UserManagerInterface() = default;
 
   virtual MultiProfileUserController* GetMultiProfileUserController() = 0;
   virtual UserImageManager* GetUserImageManager(
       const AccountId& account_id) = 0;
   virtual SupervisedUserManager* GetSupervisedUserManager() = 0;
-
-  // Method that allows to set `flow` for user identified by `account_id`.
-  // Flow should be set before login attempt.
-  // Takes ownership of the `flow`, `flow` will be deleted in case of login
-  // failure.
-  virtual void SetUserFlow(const AccountId& account_id, UserFlow* flow) = 0;
-
-  // Return user flow for current user. Returns instance of DefaultUserFlow if
-  // no flow was defined for current user, or user is not logged in.
-  // Returned value should not be cached.
-  virtual UserFlow* GetCurrentUserFlow() const = 0;
-
-  // Return user flow for user identified by `account_id`. Returns instance of
-  // DefaultUserFlow if no flow was defined for user.
-  // Returned value should not be cached.
-  virtual UserFlow* GetUserFlow(const AccountId& account_id) const = 0;
-
-  // Resets user flow for user identified by `account_id`.
-  virtual void ResetUserFlow(const AccountId& account_id) = 0;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/users/user_manager_unittest.cc b/chrome/browser/ash/login/users/user_manager_unittest.cc
index b8c546c5..c4ff7621 100644
--- a/chrome/browser/ash/login/users/user_manager_unittest.cc
+++ b/chrome/browser/ash/login/users/user_manager_unittest.cc
@@ -124,7 +124,7 @@
     settings_helper_.ReplaceDeviceSettingsProviderWithStub();
 
     // Populate the stub DeviceSettingsProvider with valid values.
-    SetDeviceSettings(false, "", false);
+    SetDeviceSettings(/* ephemeral_users_enabled= */ false, /* owner= */ "");
 
     // Instantiate ProfileHelper.
     ash::ProfileHelper::Get();
@@ -202,8 +202,7 @@
   }
 
   void SetDeviceSettings(bool ephemeral_users_enabled,
-                         const std::string& owner,
-                         bool supervised_users_enabled) {
+                         const std::string& owner) {
     settings_helper_.SetBoolean(kAccountsPrefEphemeralUsersEnabled,
                                 ephemeral_users_enabled);
     settings_helper_.SetString(kDeviceOwner, owner);
@@ -272,8 +271,9 @@
       /* exclude_list= */ std::vector<AccountId>{}));
   SetUserManagerOwnerId(EmptyAccountId());
 
-  SetDeviceSettings(false, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ false,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   EXPECT_FALSE(IsEphemeralAccountId(EmptyAccountId()));
@@ -286,8 +286,9 @@
 TEST_F(UserManagerTest, IsEphemeralAccountIdUsesEphemeralUsersEnabledPolicy) {
   EXPECT_FALSE(IsEphemeralAccountId(EmptyAccountId()));
 
-  SetDeviceSettings(true, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ true,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   EXPECT_TRUE(IsEphemeralAccountId(EmptyAccountId()));
@@ -303,16 +304,18 @@
 
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 
-  SetDeviceSettings(true, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ true,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kFollowDeviceWidePolicy);
   RetrieveTrustedDevicePolicies();
   EXPECT_TRUE(IsEphemeralAccountId(account_id));
 
-  SetDeviceSettings(false, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ false,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   RetrieveTrustedDevicePolicies();
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 }
@@ -326,16 +329,18 @@
 
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 
-  SetDeviceSettings(true, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ true,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kUnset);
   RetrieveTrustedDevicePolicies();
   EXPECT_TRUE(IsEphemeralAccountId(account_id));
 
-  SetDeviceSettings(false, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ false,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   RetrieveTrustedDevicePolicies();
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 }
@@ -349,8 +354,9 @@
 
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 
-  SetDeviceSettings(true, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ true,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kDisable);
@@ -363,14 +369,15 @@
 // Tests that `UserManager` correctly parses device-local accounts with
 // ephemeral mode equals to `kEnable` by calling
 // `IsEphemeralAccountId(account_id)` function.
-TEST_F(UserManagerTest, IsEphemeralAccountIdRespectssEnableEphemeralMode) {
+TEST_F(UserManagerTest, IsEphemeralAccountIdRespectsEnableEphemeralMode) {
   const AccountId account_id =
       CreateDeviceLocalKioskAppAccountId(kDeviceLocalAccountId);
 
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 
-  SetDeviceSettings(false, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ false,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kEnable);
@@ -476,8 +483,9 @@
   EXPECT_EQ((*users)[2]->GetAccountId(), owner_account_id_at_invalid_domain_);
 
   test_wallpaper_controller_.ClearCounts();
-  SetDeviceSettings(true, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ true,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   users = &user_manager::UserManager::Get()->GetUsers();
@@ -488,8 +496,9 @@
 }
 
 TEST_F(UserManagerTest, RegularUserLoggedInAsEphemeral) {
-  SetDeviceSettings(true, owner_account_id_at_invalid_domain_.GetUserEmail(),
-                    false);
+  SetDeviceSettings(
+      /* ephemeral_users_enabled= */ true,
+      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   user_manager::UserManager::Get()->UserLoggedIn(
diff --git a/chrome/browser/ash/login/webview_login_browsertest.cc b/chrome/browser/ash/login/webview_login_browsertest.cc
index 7e91cff..f30b1275 100644
--- a/chrome/browser/ash/login/webview_login_browsertest.cc
+++ b/chrome/browser/ash/login/webview_login_browsertest.cc
@@ -38,7 +38,6 @@
 #include "chrome/browser/ash/login/signin_partition_manager.h"
 #include "chrome/browser/ash/login/test/cryptohome_mixin.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/fake_recovery_service_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
@@ -53,6 +52,7 @@
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/scoped_test_system_nss_key_slot_mixin.h"
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_browsertest.cc b/chrome/browser/ash/policy/core/device_cloud_policy_browsertest.cc
index 742ed5c..b397cab 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_browsertest.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_browsertest.cc
@@ -15,10 +15,10 @@
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
 #include "base/values.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
diff --git a/chrome/browser/ash/policy/core/device_local_account_browsertest.cc b/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
index 1524568..357f8cb8 100644
--- a/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
@@ -51,7 +51,6 @@
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
 #include "chrome/browser/ash/login/signin_specifics.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_or_lock_screen_visible_waiter.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
@@ -71,6 +70,7 @@
 #include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
 #include "chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/session_length_limiter.h"
 #include "chrome/browser/ash/system/timezone_util.h"
diff --git a/chrome/browser/ash/policy/core/remote_commands_browsertest.cc b/chrome/browser/ash/policy/core/remote_commands_browsertest.cc
index f7cba499b..aaaa77b 100644
--- a/chrome/browser/ash/policy/core/remote_commands_browsertest.cc
+++ b/chrome/browser/ash/policy/core/remote_commands_browsertest.cc
@@ -7,9 +7,9 @@
 #include "ash/constants/ash_switches.h"
 #include "base/check.h"
 #include "base/command_line.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/ui/browser.h"
diff --git a/chrome/browser/ash/policy/core/user_policy_test_helper.cc b/chrome/browser/ash/policy/core/user_policy_test_helper.cc
index 3b4b7d6..910b584f 100644
--- a/chrome/browser/ash/policy/core/user_policy_test_helper.cc
+++ b/chrome/browser/ash/policy/core/user_policy_test_helper.cc
@@ -9,8 +9,8 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/values.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
diff --git a/chrome/browser/ash/policy/login/login_policy_test_base.h b/chrome/browser/ash/policy/login/login_policy_test_base.h
index a1d7ea1..aeb578e 100644
--- a/chrome/browser/ash/policy/login/login_policy_test_base.h
+++ b/chrome/browser/ash/policy/login/login_policy_test_base.h
@@ -8,9 +8,9 @@
 #include <memory>
 #include <string>
 
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/test/base/fake_gaia_mixin.h"
 #include "components/account_id/account_id.h"
 
diff --git a/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc b/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc
index 3e4dcdd..0bdc3801 100644
--- a/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc
+++ b/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/ash/login/existing_user_controller.h"
 #include "chrome/browser/ash/login/helper.h"
 #include "chrome/browser/ash/login/startup_utils.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_or_lock_screen_visible_waiter.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
@@ -34,6 +33,7 @@
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
 #include "chrome/browser/ash/policy/login/login_policy_test_base.h"
 #include "chrome/browser/ash/policy/login/signin_profile_extensions_policy_test_base.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/nss_service.h"
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc
index 08c3643..804e235 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc
@@ -6,42 +6,30 @@
 
 #include <memory>
 
+#include "base/containers/contains.h"
 #include "base/memory/ptr_util.h"
 #include "base/observer_list_types.h"
 #include "base/sequence_checker.h"
+#include "base/values.h"
 #include "chrome/browser/apps/app_service/metrics/app_platform_metrics.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/reporting/metrics/metric_event_observer.h"
 #include "components/reporting/proto/synced/metric_data.pb.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/protos/app_types.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace reporting {
 
-// static
-std::unique_ptr<AppEventsObserver> AppEventsObserver::CreateForProfile(
-    Profile* profile) {
-  DCHECK(profile);
-  auto app_platform_metrics_retriever =
-      std::make_unique<AppPlatformMetricsRetriever>(profile->GetWeakPtr());
-  return base::WrapUnique(
-      new AppEventsObserver(std::move(app_platform_metrics_retriever)));
-}
-
-// static
-std::unique_ptr<AppEventsObserver> AppEventsObserver::CreateForTest(
-    std::unique_ptr<AppPlatformMetricsRetriever>
-        app_platform_metrics_retriever) {
-  return base::WrapUnique(
-      new AppEventsObserver(std::move(app_platform_metrics_retriever)));
-}
-
 AppEventsObserver::AppEventsObserver(
-    std::unique_ptr<AppPlatformMetricsRetriever> app_platform_metrics_retriever)
+    std::unique_ptr<AppPlatformMetricsRetriever> app_platform_metrics_retriever,
+    const ReportingSettings* reporting_settings)
     : app_platform_metrics_retriever_(
-          std::move(app_platform_metrics_retriever)) {
+          std::move(app_platform_metrics_retriever)),
+      reporting_settings_(reporting_settings) {
   DCHECK(app_platform_metrics_retriever_);
   app_platform_metrics_retriever_->GetAppPlatformMetrics(base::BindOnce(
       &AppEventsObserver::InitEventObserver, weak_ptr_factory_.GetWeakPtr()));
@@ -56,8 +44,8 @@
 }
 
 void AppEventsObserver::SetReportingEnabled(bool is_enabled) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  is_enabled_ = is_enabled;
+  // Do nothing. We retrieve the reporting setting and validate the app type is
+  // allowed before we report observed events.
 }
 
 void AppEventsObserver::InitEventObserver(
@@ -80,7 +68,7 @@
                                        ::apps::InstallReason app_install_reason,
                                        ::apps::InstallTime app_install_time) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!is_enabled_) {
+  if (!IsAppTypeAllowed(app_type)) {
     return;
   }
 
@@ -110,7 +98,7 @@
                                       ::apps::AppType app_type,
                                       ::apps::LaunchSource app_launch_source) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!is_enabled_) {
+  if (!IsAppTypeAllowed(app_type)) {
     return;
   }
 
@@ -135,7 +123,7 @@
     ::apps::AppType app_type,
     ::apps::UninstallSource app_uninstall_source) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!is_enabled_) {
+  if (!IsAppTypeAllowed(app_type)) {
     return;
   }
 
@@ -160,4 +148,20 @@
   observer_.Reset();
 }
 
+bool AppEventsObserver::IsAppTypeAllowed(::apps::AppType app_type) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(reporting_settings_);
+  const base::Value::List* allowed_app_types = nullptr;
+  if (!reporting_settings_->GetList(::ash::reporting::kReportAppInventory,
+                                    &allowed_app_types)) {
+    // Policy likely not set. Disallow app inventory reporting regardless of app
+    // type.
+    return false;
+  }
+  const absl::optional<std::string> app_category =
+      ::ash::reporting::GetAppReportingCategoryForType(app_type);
+  return app_category.has_value() &&
+         base::Contains(*allowed_app_types, app_category.value());
+}
+
 }  // namespace reporting
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h
index 61f279d1..e67709d 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/reporting/metrics/metric_event_observer.h"
+#include "components/reporting/metrics/reporting_settings.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 
@@ -26,16 +27,9 @@
 class AppEventsObserver : public MetricEventObserver,
                           public ::apps::AppPlatformMetrics::Observer {
  public:
-  // Static helper that instantiates the `AppEventsObserver` for the given
-  // profile.
-  static std::unique_ptr<AppEventsObserver> CreateForProfile(Profile* profile);
-
-  // Static test helper that instantiates the `AppEventsObserver` using the
-  // specified `AppPlatformMetricsRetriever`.
-  static std::unique_ptr<AppEventsObserver> CreateForTest(
-      std::unique_ptr<AppPlatformMetricsRetriever>
-          app_platform_metrics_retriever);
-
+  AppEventsObserver(std::unique_ptr<AppPlatformMetricsRetriever>
+                        app_platform_metrics_retriever,
+                    const ReportingSettings* reporting_settings);
   AppEventsObserver(const AppEventsObserver& other) = delete;
   AppEventsObserver& operator=(const AppEventsObserver& other) = delete;
   ~AppEventsObserver() override;
@@ -47,9 +41,6 @@
   void SetReportingEnabled(bool is_enabled) override;
 
  private:
-  explicit AppEventsObserver(std::unique_ptr<AppPlatformMetricsRetriever>
-                                 app_platform_metrics_retriever);
-
   // Initializes events observer and starts observing app events tracked by the
   // `AppPlatformMetrics` component (if initialized).
   void InitEventObserver(::apps::AppPlatformMetrics* app_platform_metrics);
@@ -74,6 +65,10 @@
   // ::apps::AppPlatformMetrics::Observer:
   void OnAppPlatformMetricsDestroyed() override;
 
+  // Helper that determines if the specified app type is allowlisted for app
+  // inventory event reporting.
+  bool IsAppTypeAllowed(::apps::AppType app_type) const;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   // Retriever that retrieves the `AppPlatformMetrics` component so the
@@ -81,15 +76,17 @@
   const std::unique_ptr<AppPlatformMetricsRetriever>
       app_platform_metrics_retriever_;
 
+  // Pointer to the reporting settings that controls app inventory event
+  // reporting. Guaranteed to outlive the observer because it is managed by the
+  // `MetricReportingManager`.
+  const raw_ptr<const ReportingSettings> reporting_settings_;
+
   // Observer for tracking app events. Will be reset if the `AppPlatformMetrics`
   // component gets destructed before the event observer.
   base::ScopedObservation<::apps::AppPlatformMetrics,
                           ::apps::AppPlatformMetrics::Observer>
       observer_ GUARDED_BY_CONTEXT(sequence_checker_){this};
 
-  // Boolean that controls app metric collection and reporting.
-  bool is_enabled_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
-
   // Callback triggered when app metrics are collected and app metric
   // reporting is enabled.
   MetricRepeatingCallback on_metric_observed_
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc
index b13f032..3cbe2c7c 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc
@@ -4,9 +4,9 @@
 
 #include <memory>
 #include <string>
-#include <utility>
+#include <vector>
 
-#include "base/test/scoped_feature_list.h"
+#include "base/values.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_ash.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -14,11 +14,10 @@
 #include "chrome/browser/ash/policy/affiliation/affiliation_mixin.h"
 #include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
-#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
-#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
 #include "chrome/browser/policy/dm_token_utils.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
@@ -26,8 +25,8 @@
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/test/base/in_process_browser_test.h"
-#include "chromeos/ash/components/settings/cros_settings_names.h"
 #include "chromeos/dbus/missive/missive_client_test_observer.h"
+#include "components/prefs/pref_service.h"
 #include "components/reporting/proto/synced/metric_data.pb.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
@@ -88,7 +87,6 @@
  protected:
   AppEventsObserverBrowserTest() {
     crypto_home_mixin_.MarkUserAsExisting(affiliation_mixin_.account_id());
-    scoped_feature_list_.InitAndEnableFeature(kEnableAppMetricsReporting);
     ::policy::SetDMTokenForTesting(
         ::policy::DMToken::CreateValidToken(kDMToken));
   }
@@ -101,7 +99,6 @@
 
   void SetUpOnMainThread() override {
     ::policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
-    SetPolicyEnabled(true);
     if (content::IsPreTest()) {
       // Preliminary setup - set up affiliated user.
       ::policy::AffiliationTestHelper::PreLoginUser(
@@ -111,6 +108,7 @@
 
     // Login as affiliated user otherwise.
     ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
+    SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryPWA});
   }
 
   // Helper that installs a standalone webapp with the specified start url.
@@ -130,9 +128,13 @@
         app_id, ::apps::UninstallSource::kAppList);
   }
 
-  void SetPolicyEnabled(bool is_enabled) {
-    scoped_testing_cros_settings_.device_settings()->SetBoolean(
-        ::ash::kReportDeviceAppInfo, is_enabled);
+  void SetAllowedAppReportingTypes(const std::vector<std::string>& app_types) {
+    base::Value::List allowed_app_types;
+    for (const auto& app_type : app_types) {
+      allowed_app_types.Append(app_type);
+    }
+    profile()->GetPrefs()->SetList(::ash::reporting::kReportAppInventory,
+                                   std::move(allowed_app_types));
   }
 
   Profile* profile() const {
@@ -143,9 +145,6 @@
   ::policy::DevicePolicyCrosTestHelper test_helper_;
   ::policy::AffiliationMixin affiliation_mixin_{&mixin_host_, &test_helper_};
   ::ash::CryptohomeMixin crypto_home_mixin_{&mixin_host_};
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-  ::ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
 };
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, PRE_ReportInstalledApp) {
@@ -154,7 +153,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportInstalledApp) {
-  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableAppMetricsReporting));
   ::chromeos::MissiveClientTestObserver missive_observer(base::BindRepeating(
       &IsMetricEventOfType, MetricEventType::APP_INSTALLED));
   const auto& app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
@@ -184,7 +182,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportLaunchedApp) {
-  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableAppMetricsReporting));
   const auto& app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
   ::chromeos::MissiveClientTestObserver missive_observer(
       base::BindRepeating(&IsMetricEventOfType, MetricEventType::APP_LAUNCHED));
@@ -209,7 +206,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportUninstalledApp) {
-  ASSERT_TRUE(base::FeatureList::IsEnabled(kEnableAppMetricsReporting));
   const auto& app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
   ::chromeos::MissiveClientTestObserver missive_observer(base::BindRepeating(
       &IsMetricEventOfType, MetricEventType::APP_UNINSTALLED));
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc
index c28cd81..3929986 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc
@@ -5,9 +5,10 @@
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h"
 
 #include <memory>
-#include <tuple>
+#include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/values.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/launch_result_type.h"
@@ -15,6 +16,8 @@
 #include "chrome/browser/apps/app_service/metrics/app_platform_metrics_service_test_base.h"
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
+#include "components/reporting/metrics/fakes/fake_reporting_settings.h"
 #include "components/reporting/proto/synced/metric_data.pb.h"
 #include "components/reporting/util/test_support_callbacks.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
@@ -79,8 +82,7 @@
               (override));
 };
 
-class AppEventsObserverTest : public ::apps::AppPlatformMetricsServiceTestBase,
-                              public ::testing::WithParamInterface<bool> {
+class AppEventsObserverTest : public ::apps::AppPlatformMetricsServiceTestBase {
  protected:
   void SetUp() override {
     ::apps::AppPlatformMetricsServiceTestBase::SetUp();
@@ -98,9 +100,8 @@
           std::move(callback).Run(
               app_platform_metrics_service()->AppPlatformMetrics());
         });
-    app_events_observer_ = AppEventsObserver::CreateForTest(
-        std::move(mock_app_platform_metrics_retriever));
-    app_events_observer_->SetReportingEnabled(IsReportingEnabled());
+    app_events_observer_ = std::make_unique<AppEventsObserver>(
+        std::move(mock_app_platform_metrics_retriever), &reporting_settings_);
   }
 
   void TearDown() override {
@@ -108,12 +109,25 @@
     ::apps::AppPlatformMetricsServiceTestBase::TearDown();
   }
 
-  bool IsReportingEnabled() const { return GetParam(); }
+  void SetAllowedAppReportingTypes(const std::vector<std::string>& app_types) {
+    base::Value::List allowed_app_types;
+    for (const auto& app_type : app_types) {
+      allowed_app_types.Append(app_type);
+    }
+    reporting_settings_.SetList(::ash::reporting::kReportAppInventory,
+                                std::move(allowed_app_types));
 
+    // Simulate policy update.
+    bool is_app_reporting_enabled = !app_types.empty();
+    app_events_observer_->SetReportingEnabled(is_app_reporting_enabled);
+  }
+
+  test::FakeReportingSettings reporting_settings_;
   std::unique_ptr<AppEventsObserver> app_events_observer_;
 };
 
-TEST_P(AppEventsObserverTest, OnAppInstalled) {
+TEST_F(AppEventsObserverTest, OnAppInstalled) {
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryBrowser});
   test::TestEvent<MetricData> test_event;
   app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
 
@@ -123,37 +137,63 @@
                 /*publisher_id=*/"", ::apps::Readiness::kReady,
                 ::apps::InstallSource::kBrowser);
 
-  if (IsReportingEnabled()) {
-    // Verify data being reported.
-    const MetricData& result = test_event.result();
-    ASSERT_TRUE(result.has_event_data());
-    EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_INSTALLED));
-    ASSERT_TRUE(result.has_telemetry_data());
-    ASSERT_TRUE(result.telemetry_data().has_app_telemetry());
-    ASSERT_TRUE(result.telemetry_data().app_telemetry().has_app_install_data());
+  // Verify data being reported.
+  const MetricData& result = test_event.result();
+  ASSERT_TRUE(result.has_event_data());
+  EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_INSTALLED));
+  ASSERT_TRUE(result.has_telemetry_data());
+  ASSERT_TRUE(result.telemetry_data().has_app_telemetry());
+  ASSERT_TRUE(result.telemetry_data().app_telemetry().has_app_install_data());
 
-    const AppInstallData& app_install_data =
-        result.telemetry_data().app_telemetry().app_install_data();
-    EXPECT_THAT(app_install_data.app_id(), StrEq(app_id));
-    EXPECT_THAT(
-        app_install_data.app_type(),
-        Eq(::apps::ApplicationType::APPLICATION_TYPE_STANDALONE_BROWSER));
-    EXPECT_THAT(
-        app_install_data.app_install_reason(),
-        Eq(::apps::ApplicationInstallReason::APPLICATION_INSTALL_REASON_USER));
-    EXPECT_THAT(app_install_data.app_install_source(),
-                Eq(::apps::ApplicationInstallSource::
-                       APPLICATION_INSTALL_SOURCE_BROWSER));
-    EXPECT_THAT(
-        app_install_data.app_install_time(),
-        Eq(::apps::ApplicationInstallTime::APPLICATION_INSTALL_TIME_INIT));
-  } else {
-    // Should not report any data if reporting is disabled.
-    ASSERT_TRUE(test_event.no_result());
-  }
+  const AppInstallData& app_install_data =
+      result.telemetry_data().app_telemetry().app_install_data();
+  EXPECT_THAT(app_install_data.app_id(), StrEq(app_id));
+  EXPECT_THAT(app_install_data.app_type(),
+              Eq(::apps::ApplicationType::APPLICATION_TYPE_STANDALONE_BROWSER));
+  EXPECT_THAT(
+      app_install_data.app_install_reason(),
+      Eq(::apps::ApplicationInstallReason::APPLICATION_INSTALL_REASON_USER));
+  EXPECT_THAT(
+      app_install_data.app_install_source(),
+      Eq(::apps::ApplicationInstallSource::APPLICATION_INSTALL_SOURCE_BROWSER));
+  EXPECT_THAT(
+      app_install_data.app_install_time(),
+      Eq(::apps::ApplicationInstallTime::APPLICATION_INSTALL_TIME_INIT));
 }
 
-TEST_P(AppEventsObserverTest, OnAppLaunched) {
+TEST_F(AppEventsObserverTest, OnAppInstalled_UnsetPolicy) {
+  test::TestEvent<MetricData> test_event;
+  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+
+  // Install new app.
+  static constexpr char app_id[] = "TestNewApp";
+  InstallOneApp(app_id, ::apps::AppType::kStandaloneBrowser,
+                /*publisher_id=*/"", ::apps::Readiness::kReady,
+                ::apps::InstallSource::kBrowser);
+
+  // Verify no data is being reported.
+  ASSERT_TRUE(test_event.no_result());
+}
+
+TEST_F(AppEventsObserverTest, OnAppInstalled_DisallowedAppType) {
+  // Set policy to enable reporting for a different app type than the one being
+  // tested.
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
+  test::TestEvent<MetricData> test_event;
+  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+
+  // Install new app.
+  static constexpr char app_id[] = "TestNewApp";
+  InstallOneApp(app_id, ::apps::AppType::kStandaloneBrowser,
+                /*publisher_id=*/"", ::apps::Readiness::kReady,
+                ::apps::InstallSource::kBrowser);
+
+  // Verify no data is being reported.
+  ASSERT_TRUE(test_event.no_result());
+}
+
+TEST_F(AppEventsObserverTest, OnAppLaunched) {
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
   test::TestEvent<MetricData> test_event;
   app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
 
@@ -164,30 +204,59 @@
   proxy->Launch(kTestAppId, ui::EF_NONE, apps::LaunchSource::kFromCommandLine,
                 nullptr);
 
-  if (IsReportingEnabled()) {
-    // Verify data being reported.
-    const MetricData& result = test_event.result();
-    ASSERT_TRUE(result.has_event_data());
-    EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_LAUNCHED));
-    ASSERT_TRUE(result.has_telemetry_data());
-    ASSERT_TRUE(result.telemetry_data().has_app_telemetry());
-    ASSERT_TRUE(result.telemetry_data().app_telemetry().has_app_launch_data());
+  // Verify data being reported.
+  const MetricData& result = test_event.result();
+  ASSERT_TRUE(result.has_event_data());
+  EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_LAUNCHED));
+  ASSERT_TRUE(result.has_telemetry_data());
+  ASSERT_TRUE(result.telemetry_data().has_app_telemetry());
+  ASSERT_TRUE(result.telemetry_data().app_telemetry().has_app_launch_data());
 
-    const AppLaunchData& app_launch_data =
-        result.telemetry_data().app_telemetry().app_launch_data();
-    EXPECT_THAT(app_launch_data.app_id(), StrEq(kTestAppId));
-    EXPECT_THAT(app_launch_data.app_type(),
-                Eq(::apps::ApplicationType::APPLICATION_TYPE_ARC));
-    EXPECT_THAT(app_launch_data.app_launch_source(),
-                Eq(::apps::ApplicationLaunchSource::
-                       APPLICATION_LAUNCH_SOURCE_COMMAND_LINE));
-  } else {
-    // Should not report any data if reporting is disabled.
-    ASSERT_TRUE(test_event.no_result());
-  }
+  const AppLaunchData& app_launch_data =
+      result.telemetry_data().app_telemetry().app_launch_data();
+  EXPECT_THAT(app_launch_data.app_id(), StrEq(kTestAppId));
+  EXPECT_THAT(app_launch_data.app_type(),
+              Eq(::apps::ApplicationType::APPLICATION_TYPE_ARC));
+  EXPECT_THAT(app_launch_data.app_launch_source(),
+              Eq(::apps::ApplicationLaunchSource::
+                     APPLICATION_LAUNCH_SOURCE_COMMAND_LINE));
 }
 
-TEST_P(AppEventsObserverTest, OnAppUninstalled) {
+TEST_F(AppEventsObserverTest, OnAppLaunched_UnsetPolicy) {
+  test::TestEvent<MetricData> test_event;
+  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+
+  // Simulate app launch for pre-installed app.
+  auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
+  proxy->SetAppPlatformMetricsServiceForTesting(GetAppPlatformMetricsService());
+  FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
+  proxy->Launch(kTestAppId, ui::EF_NONE, apps::LaunchSource::kFromCommandLine,
+                nullptr);
+
+  // Verify no data is being reported.
+  ASSERT_TRUE(test_event.no_result());
+}
+
+TEST_F(AppEventsObserverTest, OnAppLaunched_DisallowedAppType) {
+  // Set policy to enable reporting for a different app type than the one being
+  // tested.
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryGames});
+  test::TestEvent<MetricData> test_event;
+  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+
+  // Simulate app launch for pre-installed app.
+  auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
+  proxy->SetAppPlatformMetricsServiceForTesting(GetAppPlatformMetricsService());
+  FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
+  proxy->Launch(kTestAppId, ui::EF_NONE, apps::LaunchSource::kFromCommandLine,
+                nullptr);
+
+  // Verify no data is being reported.
+  ASSERT_TRUE(test_event.no_result());
+}
+
+TEST_F(AppEventsObserverTest, OnAppUninstalled) {
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
   test::TestEvent<MetricData> test_event;
   app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
 
@@ -197,32 +266,57 @@
   FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
   proxy->UninstallSilently(kTestAppId, ::apps::UninstallSource::kAppList);
 
-  if (IsReportingEnabled()) {
-    // Verify data being reported.
-    const MetricData& result = test_event.result();
-    ASSERT_TRUE(result.has_event_data());
-    EXPECT_THAT(result.event_data().type(),
-                Eq(MetricEventType::APP_UNINSTALLED));
-    ASSERT_TRUE(result.has_telemetry_data());
-    ASSERT_TRUE(result.telemetry_data().has_app_telemetry());
-    ASSERT_TRUE(
-        result.telemetry_data().app_telemetry().has_app_uninstall_data());
+  // Verify data being reported.
+  const MetricData& result = test_event.result();
+  ASSERT_TRUE(result.has_event_data());
+  EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_UNINSTALLED));
+  ASSERT_TRUE(result.has_telemetry_data());
+  ASSERT_TRUE(result.telemetry_data().has_app_telemetry());
+  ASSERT_TRUE(result.telemetry_data().app_telemetry().has_app_uninstall_data());
 
-    const AppUninstallData& app_uninstall_data =
-        result.telemetry_data().app_telemetry().app_uninstall_data();
-    EXPECT_THAT(app_uninstall_data.app_id(), StrEq(kTestAppId));
-    EXPECT_THAT(app_uninstall_data.app_type(),
-                Eq(::apps::ApplicationType::APPLICATION_TYPE_ARC));
-    EXPECT_THAT(app_uninstall_data.app_uninstall_source(),
-                Eq(::apps::ApplicationUninstallSource::
-                       APPLICATION_UNINSTALL_SOURCE_APP_LIST));
-  } else {
-    // Should not report any data if reporting is disabled.
-    ASSERT_TRUE(test_event.no_result());
-  }
+  const AppUninstallData& app_uninstall_data =
+      result.telemetry_data().app_telemetry().app_uninstall_data();
+  EXPECT_THAT(app_uninstall_data.app_id(), StrEq(kTestAppId));
+  EXPECT_THAT(app_uninstall_data.app_type(),
+              Eq(::apps::ApplicationType::APPLICATION_TYPE_ARC));
+  EXPECT_THAT(app_uninstall_data.app_uninstall_source(),
+              Eq(::apps::ApplicationUninstallSource::
+                     APPLICATION_UNINSTALL_SOURCE_APP_LIST));
 }
 
-TEST_P(AppEventsObserverTest, OnAppPlatformMetricsDestroyed) {
+TEST_F(AppEventsObserverTest, OnAppUninstalled_UnsetPolicy) {
+  test::TestEvent<MetricData> test_event;
+  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+
+  // Simulate app uninstall for pre-installed app.
+  auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
+  proxy->SetAppPlatformMetricsServiceForTesting(GetAppPlatformMetricsService());
+  FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
+  proxy->UninstallSilently(kTestAppId, ::apps::UninstallSource::kAppList);
+
+  // Verify no data is being reported.
+  ASSERT_TRUE(test_event.no_result());
+}
+
+TEST_F(AppEventsObserverTest, OnAppUninstalled_DisallowedAppType) {
+  // Set policy to enable reporting for a different app type than the one being
+  // tested.
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryGames});
+  test::TestEvent<MetricData> test_event;
+  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+
+  // Simulate app uninstall for pre-installed app.
+  auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
+  proxy->SetAppPlatformMetricsServiceForTesting(GetAppPlatformMetricsService());
+  FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
+  proxy->UninstallSilently(kTestAppId, ::apps::UninstallSource::kAppList);
+
+  // Verify no data is being reported.
+  ASSERT_TRUE(test_event.no_result());
+}
+
+TEST_F(AppEventsObserverTest, OnAppPlatformMetricsDestroyed) {
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryBrowser});
   test::TestEvent<MetricData> test_event;
   app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
 
@@ -239,9 +333,5 @@
   ASSERT_TRUE(test_event.no_result());
 }
 
-INSTANTIATE_TEST_SUITE_P(AppEventsObserverTests,
-                         AppEventsObserverTest,
-                         ::testing::Bool() /* true - reporting enabled*/);
-
 }  // namespace
 }  // namespace reporting
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc
index a74e6bf3..07c8c7c 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc
@@ -20,6 +20,7 @@
 #include "base/values.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_usage_collector.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_usage_telemetry_sampler.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/audio/audio_events_observer.h"
@@ -33,6 +34,7 @@
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_memory_sampler_handler.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_sampler_handler.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/device_activity/device_activity_sampler.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/network/https_latency_event_detector.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/network/https_latency_sampler.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.h"
@@ -306,20 +308,21 @@
       /*init_delay=*/base::TimeDelta());
   InitPeripheralsCollectors();
 
-  // Start observing app events only if the feature flag is set and app service
-  // is available for the given profile.
-  if (base::FeatureList::IsEnabled(kEnableAppMetricsReporting) &&
-      delegate_->IsAppServiceAvailableForProfile(profile)) {
+  // Start observing app events only if the app service is available for the
+  // given profile.
+  if (delegate_->IsAppServiceAvailableForProfile(profile)) {
     // Initialize the `AppUsageCollector` so we can start tracking app usage
     // reports right away.
     app_usage_collector_ =
         AppUsageCollector::Create(profile, &reporting_settings_);
-    auto app_events_observer = AppEventsObserver::CreateForProfile(profile);
+    auto app_events_observer = std::make_unique<AppEventsObserver>(
+        std::make_unique<AppPlatformMetricsRetriever>(profile->GetWeakPtr()),
+        user_reporting_settings_.get());
     InitEventObserverManager(
         std::move(app_events_observer), user_event_report_queue_.get(),
-        &reporting_settings_,
-        /*enable_setting_path=*/::ash::kReportDeviceAppInfo,
-        metrics::kReportDeviceAppInfoDefaultValue,
+        user_reporting_settings_.get(),
+        /*enable_setting_path=*/::ash::reporting::kReportAppInventory,
+        metrics::kReportAppInventoryEnabledDefaultValue,
         /*init_delay=*/base::TimeDelta());
   }
 }
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc
index 08c58ba..e19d811 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc
@@ -496,10 +496,6 @@
 
 TEST_F(MetricReportingManagerEventTest,
        ShouldNotCreateAppEventObserverWhenAppServiceUnavailable) {
-  // Enable app metrics reporting feature flag.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(kEnableAppMetricsReporting);
-
   // Setup appropriate mocks and stubs.
   auto fake_reporting_settings =
       std::make_unique<test::FakeReportingSettings>();
@@ -592,20 +588,6 @@
           /*is_affiliated=*/true, app_event_settings,
           /*has_init_delay=*/false,
           /*expected_count_before_login=*/0,
-          /*expected_count_after_login=*/0},
-         {"AppEvents_FeatureFlagEnabled",
-          /*enabled_features=*/{kEnableAppMetricsReporting},
-          /*disabled_features=*/{},
-          /*is_affiliated=*/true, app_event_settings,
-          /*has_init_delay=*/false,
-          /*expected_count_before_login=*/0,
-          /*expected_count_after_login=*/1},
-         {"AppEvents_FeatureFlagDisabled",
-          /*enabled_features=*/{},
-          /*disabled_features=*/{kEnableAppMetricsReporting},
-          /*is_affiliated=*/true, app_event_settings,
-          /*has_init_delay=*/false,
-          /*expected_count_before_login=*/0,
           /*expected_count_after_login=*/0}}),
     [](const testing::TestParamInfo<MetricReportingManagerInfoTest::ParamType>&
            info) { return info.param.test_name; });
@@ -996,7 +978,7 @@
   ON_CALL(*mock_delegate_, IsDeprovisioned).WillByDefault(Return(false));
   ON_CALL(*mock_delegate_, IsAffiliated).WillByDefault(Return(true));
   ON_CALL(*mock_delegate_, IsAppServiceAvailableForProfile)
-      .WillByDefault(Return(true));
+      .WillByDefault(Return(false));
 
   auto metric_reporting_manager = MetricReportingManager::CreateForTesting(
       std::move(mock_delegate_), nullptr);
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
index fcd6e91a..0f8dc08 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
@@ -7,6 +7,8 @@
 #include "base/values.h"
 #include "chrome/browser/chromeos/reporting/metric_default_utils.h"
 #include "components/pref_registry/pref_registry_syncable.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash::reporting {
 
@@ -20,4 +22,34 @@
           .InMilliseconds());
 }
 
+absl::optional<std::string> GetAppReportingCategoryForType(
+    ::apps::AppType app_type) {
+  switch (app_type) {
+    case ::apps::AppType::kArc:
+      return kAppCategoryAndroidApps;
+    case ::apps::AppType::kBuiltIn:
+    case ::apps::AppType::kSystemWeb:
+      return kAppCategorySystemApps;
+    case ::apps::AppType::kCrostini:
+    case ::apps::AppType::kBruschetta:
+      return kAppCategoryLinuxApps;
+    case ::apps::AppType::kChromeApp:
+    case ::apps::AppType::kRemote:
+    case ::apps::AppType::kStandaloneBrowserChromeApp:
+    case ::apps::AppType::kExtension:
+    case ::apps::AppType::kStandaloneBrowserExtension:
+      return kAppCategoryChromeAppsExtensions;
+    case ::apps::AppType::kWeb:
+      return kAppCategoryPWA;
+    case ::apps::AppType::kStandaloneBrowser:
+      return kAppCategoryBrowser;
+    case ::apps::AppType::kBorealis:
+      return kAppCategoryGames;
+    case ::apps::AppType::kMacOs:     // Not tracked today.
+    case ::apps::AppType::kPluginVm:  // Only applies to MGS, so we skip.
+    case ::apps::AppType::kUnknown:   // Invalid app type.
+      return absl::nullopt;
+  }
+}
+
 }  // namespace ash::reporting
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
index 57995b5..74c41ef 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
@@ -5,10 +5,16 @@
 #ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_REPORTING_PREFS_H_
 #define CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_REPORTING_PREFS_H_
 
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
 namespace user_prefs {
 class PrefRegistrySyncable;
 }
 
+namespace apps {
+enum class AppType;
+}
+
 namespace ash::reporting {
 
 // A list pref that controls app inventory event reporting for the specified app
@@ -24,8 +30,23 @@
 constexpr char kReportAppUsageCollectionRateMs[] =
     "reporting.report_app_usage_collection_rate_ms";
 
+// Application category types tracked by the app metric reporting user policies.
+constexpr char kAppCategoryAndroidApps[] = "android_apps";
+constexpr char kAppCategoryBrowser[] = "browser";
+constexpr char kAppCategoryChromeAppsExtensions[] =
+    "chrome_apps_and_extensions";
+constexpr char kAppCategoryGames[] = "games";
+constexpr char kAppCategoryLinuxApps[] = "linux_apps";
+constexpr char kAppCategoryPWA[] = "progressive_web_apps";
+constexpr char kAppCategorySystemApps[] = "system_apps";
+
 void RegisterProfilePrefs(::user_prefs::PrefRegistrySyncable* registry);
 
+// Gets the corresponding app metric reporting category for the specified app
+// type.
+absl::optional<std::string> GetAppReportingCategoryForType(
+    ::apps::AppType app_type);
+
 }  // namespace ash::reporting
 
 #endif  // CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_REPORTING_PREFS_H_
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/https_latency_events_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/https_latency_events_unittest.cc
index 18fac29..a391427 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/https_latency_events_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/https_latency_events_unittest.cc
@@ -85,6 +85,10 @@
 
   bool IsDeprovisioned() const override { return false; }
 
+  bool IsAppServiceAvailableForProfile(Profile* profile) const override {
+    return false;
+  }
+
   std::unique_ptr<MetricReportQueue> CreateMetricReportQueue(
       EventType event_type,
       Destination destination,
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.cc
index 028b8b3a..73f90dc 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.cc
@@ -14,7 +14,9 @@
 #include "base/logging.h"
 #include "base/task/bind_post_task.h"
 #include "chrome/browser/ash/net/network_health/network_health_manager.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/network/wifi_signal_strength_rssi_fetcher.h"
+#include "chromeos/ash/components/network/network_handler.h"
 #include "chromeos/ash/components/network/network_state.h"
 #include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_type_pattern.h"
@@ -51,24 +53,27 @@
 
 NetworkEventsObserver::~NetworkEventsObserver() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!ash::NetworkHandler::IsInitialized() ||
+      !ash::NetworkHandler::Get()->network_state_handler()) {
+    return;
+  }
+  ash::NetworkHandler::Get()->network_state_handler()->RemoveObserver(this);
 }
 
-void NetworkEventsObserver::OnConnectionStateChanged(
-    const std::string& guid,
-    chromeos::network_health::mojom::NetworkState state) {
-  using NetworkStateMojom = chromeos::network_health::mojom::NetworkState;
+void NetworkEventsObserver::NetworkConnectionStateChanged(
+    const ash::NetworkState* network) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  const auto* network_state = ::ash::NetworkHandler::Get()
-                                  ->network_state_handler()
-                                  ->GetNetworkStateFromGuid(guid);
-  if (!network_state) {
+  if (!network) {
     return;
   }
 
+  NetworkConnectionState state =
+      NetworkTelemetrySampler::GetNetworkConnectionState(network);
+
   MetricData metric_data;
   const auto network_type =
-      ::ash::NetworkTypePattern::Primitive(network_state->type());
+      ::ash::NetworkTypePattern::Primitive(network->type());
+
   if (network_type.MatchesPattern(ash::NetworkTypePattern::Physical()) &&
       base::FeatureList::IsEnabled(
           kEnableNetworkConnectionStateEventsReporting)) {
@@ -80,48 +85,53 @@
     metric_data.mutable_event_data()->set_type(
         MetricEventType::VPN_CONNECTION_STATE_CHANGE);
   } else {
+    // Only report and update `active_connection_state_map_` for VPN and
+    // physical networks.
     return;
   }
 
-  if (base::Contains(connection_state_map_, guid) &&
-      connection_state_map_.at(guid) == state) {
-    DVLOG(1) << "Connection state already reported";
+  const bool active_network =
+      base::Contains(active_connection_state_map_, network->guid());
+
+  // If network was not found in the map, this means either it was already
+  // reported as NOT_CONNECTED or it is newly added in a disconnected state and
+  // we don't want to report the event in both cases. If network was found in
+  // the map, make sure that it had a different state than the one previously
+  // reported.
+  if ((!active_network && state == NetworkConnectionState::NOT_CONNECTED) ||
+      (active_network &&
+       active_connection_state_map_.at(network->guid()) == state)) {
+    // No connection state change to report.
     return;
   }
-  connection_state_map_[guid] = state;
 
+  if (state != NetworkConnectionState::NOT_CONNECTED) {
+    // Update active network connection state.
+    active_connection_state_map_[network->guid()] = state;
+  } else if (active_network) {
+    // Network was active but now disconnected, remove from map as we only
+    // maintain currently active connections.
+    active_connection_state_map_.erase(network->guid());
+  }
   auto* const connection_change_data =
       metric_data.mutable_telemetry_data()
           ->mutable_networks_telemetry()
           ->mutable_network_connection_change_event_data();
-  connection_change_data->set_guid(guid);
-  switch (state) {
-    case NetworkStateMojom::kOnline:
-      connection_change_data->set_connection_state(
-          NetworkConnectionState::ONLINE);
-      break;
-    case NetworkStateMojom::kConnected:
-      connection_change_data->set_connection_state(
-          NetworkConnectionState::CONNECTED);
-      break;
-    case NetworkStateMojom::kPortal:
-      connection_change_data->set_connection_state(
-          NetworkConnectionState::PORTAL);
-      break;
-    case NetworkStateMojom::kConnecting:
-      connection_change_data->set_connection_state(
-          NetworkConnectionState::CONNECTING);
-      break;
-    case NetworkStateMojom::kNotConnected:
-      connection_change_data->set_connection_state(
-          NetworkConnectionState::NOT_CONNECTED);
-      break;
-    default:
-      NOTREACHED();
-  }
+  connection_change_data->set_guid(network->guid());
+  connection_change_data->set_connection_state(state);
   OnEventObserved(std::move(metric_data));
 }
 
+void NetworkEventsObserver::OnShuttingDown() {
+  ash::NetworkHandler::Get()->network_state_handler()->RemoveObserver(this);
+}
+
+void NetworkEventsObserver::OnConnectionStateChanged(
+    const std::string& guid,
+    chromeos::network_health::mojom::NetworkState state) {
+  // |NetworkConnectionStateChanged| is used instead.
+}
+
 void NetworkEventsObserver::OnSignalStrengthChanged(
     const std::string& guid,
     chromeos::network_health::mojom::UInt32ValuePtr signal_strength) {
@@ -148,15 +158,43 @@
       BindNewPipeAndPassRemote());
 }
 
+void NetworkEventsObserver::SetNetworkConnectionObservation(bool is_enabled) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  active_connection_state_map_.clear();
+  if (!ash::NetworkHandler::IsInitialized() ||
+      !ash::NetworkHandler::Get()->network_state_handler()) {
+    LOG(ERROR)
+        << "Cannot set network connection events observation for reporting. "
+           "Network state handler is not initialized.";
+    return;
+  }
+  if (!is_enabled) {
+    ash::NetworkHandler::Get()->network_state_handler()->RemoveObserver(this);
+    return;
+  }
+
+  ash::NetworkStateHandler::NetworkStateList network_state_list;
+  ash::NetworkHandler::Get()
+      ->network_state_handler()
+      ->GetActiveNetworkListByType(
+          ash::NetworkTypePattern::Physical() | ash::NetworkTypePattern::VPN(),
+          &network_state_list);
+  for (const auto* network : network_state_list) {
+    active_connection_state_map_[network->guid()] =
+        NetworkTelemetrySampler::GetNetworkConnectionState(network);
+  }
+  ash::NetworkHandler::Get()->network_state_handler()->AddObserver(this);
+}
+
 void NetworkEventsObserver::SetReportingEnabled(bool is_enabled) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   MojoServiceEventsObserverBase<
       ::chromeos::network_health::mojom::NetworkEventsObserver>::
       SetReportingEnabled(is_enabled);
+  SetNetworkConnectionObservation(is_enabled);
   if (!is_enabled) {
-    // Reset connection state fields.
-    connection_state_map_.clear();
     return;
   }
 
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.h b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.h
index 6f1b0ae..a207fff 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer.h
@@ -12,6 +12,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/mojo_service_events_observer_base.h"
 #include "chromeos/ash/components/network/network_state.h"
+#include "chromeos/ash/components/network/network_state_handler_observer.h"
 #include "chromeos/services/network_health/public/mojom/network_health.mojom.h"
 
 namespace reporting {
@@ -23,7 +24,8 @@
 class NetworkEventsObserver
     : public ::chromeos::network_health::mojom::NetworkEventsObserver,
       public MojoServiceEventsObserverBase<
-          ::chromeos::network_health::mojom::NetworkEventsObserver> {
+          ::chromeos::network_health::mojom::NetworkEventsObserver>,
+      public ash::NetworkStateHandlerObserver {
  public:
   NetworkEventsObserver();
 
@@ -32,6 +34,10 @@
 
   ~NetworkEventsObserver() override;
 
+  // ash::NetworkStateHandlerObserver:
+  void NetworkConnectionStateChanged(const ash::NetworkState* network) override;
+  void OnShuttingDown() override;
+
   // ::chromeos::network_health::mojom::NetworkEventsObserver:
   void OnConnectionStateChanged(
       const std::string& guid,
@@ -50,6 +56,8 @@
   void AddObserver() override;
 
  private:
+  void SetNetworkConnectionObservation(bool is_enabled);
+
   void CheckForSignalStrengthEvent(const ash::NetworkState* network_state);
 
   void OnSignalStrengthChangedRssiValueReceived(
@@ -61,9 +69,12 @@
 
   bool low_signal_reported_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
 
-  // Connection state events map.
-  base::flat_map<std::string, ::chromeos::network_health::mojom::NetworkState>
-      connection_state_map_ GUARDED_BY_CONTEXT(sequence_checker_);
+  // Map of active networks' guids to connection states, where active means that
+  // the network is in portal, connecting, connected or online state. Only
+  // maintain map of active network connections to avoid having the map growing
+  // very large.
+  base::flat_map<std::string, NetworkConnectionState>
+      active_connection_state_map_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   base::WeakPtrFactory<NetworkEventsObserver> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer_unittest.cc
index 477429c7..1347b05 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_events_observer_unittest.cc
@@ -15,9 +15,14 @@
 #include "base/test/repeating_test_future.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "base/values.h"
 #include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
+#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
 #include "chromeos/ash/components/login/login_state/login_state.h"
+#include "chromeos/ash/components/network/managed_state.h"
+#include "chromeos/ash/components/network/network_handler.h"
 #include "chromeos/ash/components/network/network_handler_test_helper.h"
+#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/tether_constants.h"
 #include "chromeos/services/network_health/public/mojom/network_health_types.mojom.h"
 #include "components/reporting/proto/synced/metric_data.pb.h"
@@ -27,6 +32,7 @@
 
 using chromeos::network_health::mojom::NetworkState;
 using testing::Eq;
+using testing::StrEq;
 
 namespace reporting {
 namespace {
@@ -93,7 +99,7 @@
                                /*visible=*/true);
     service_client->AddService(kCellularServicePath, kCellularGuid,
                                "cellular-network-name", shill::kTypeCellular,
-                               shill::kStateReady, /*visible=*/true);
+                               shill::kStateAssociation, /*visible=*/true);
     service_client->SetServiceProperty(
         kCellularServicePath, shill::kIccidProperty, base::Value("test_iccid"));
     task_environment_.RunUntilIdle();
@@ -345,11 +351,14 @@
 
 struct NetworkConnectionStateTestCase {
   std::string test_name;
+  std::string service_path;
   std::string guid;
-  NetworkState input_state;
-  NetworkState other_state = NetworkState::kNotConnected;
+  std::string input_state;
+  std::string other_state = shill::kStateIdle;
   MetricEventType expected_event_type;
   NetworkConnectionState expected_state;
+  NetworkConnectionState other_expected_state =
+      NetworkConnectionState::NOT_CONNECTED;
 };
 
 class NetworkEventsObserverConnectionStateTest
@@ -359,6 +368,11 @@
 
   void TearDown() override { network_events_observer_test_helper_.TearDown(); }
 
+  ash::ShillServiceClient::TestInterface* service_client() {
+    return network_events_observer_test_helper_.network_handler_test_helper()
+        ->service_test();
+  }
+
   NetworkEventsObserverTestHelper network_events_observer_test_helper_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
@@ -368,23 +382,29 @@
       /*enabled_features=*/{kEnableVpnConnectionStateEventsReporting},
       /*disabled_features=*/{kEnableNetworkConnectionStateEventsReporting});
 
-  bool event_reported = false;
-
   NetworkEventsObserver network_events_observer;
-  MetricData result_metric_data;
-  auto cb =
-      base::BindLambdaForTesting([&](MetricData) { event_reported = true; });
+  network_events_observer.SetReportingEnabled(true);
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  network_events_observer.SetOnEventObservedCallback(test_future.GetCallback());
 
-  network_events_observer.SetOnEventObservedCallback(std::move(cb));
-  network_events_observer.OnConnectionStateChanged(kWifiGuid,
-                                                   NetworkState::kNotConnected);
+  service_client()->SetServiceProperty(kWifiServicePath, shill::kStateProperty,
+                                       base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
 
-  ASSERT_FALSE(event_reported);
+  ASSERT_TRUE(test_future.IsEmpty());
 
-  network_events_observer.OnConnectionStateChanged(kVpnGuid,
-                                                   NetworkState::kNotConnected);
+  service_client()->SetServiceProperty(kVpnServicePath, shill::kStateProperty,
+                                       base::Value(shill::kStateOnline));
 
-  EXPECT_TRUE(event_reported);
+  MetricData metric_data = test_future.Take();
+  EXPECT_THAT(metric_data.event_data().type(),
+              Eq(MetricEventType::VPN_CONNECTION_STATE_CHANGE));
+  EXPECT_THAT(metric_data.telemetry_data()
+                  .networks_telemetry()
+                  .network_connection_change_event_data()
+                  .connection_state(),
+              Eq(NetworkConnectionState::ONLINE));
+  EXPECT_TRUE(test_future.IsEmpty());
 }
 
 TEST_F(NetworkEventsObserverConnectionStateTest, VpnFeatureDisabled) {
@@ -394,23 +414,84 @@
       /*disabled_features=*/
       {kEnableVpnConnectionStateEventsReporting});
 
-  bool event_reported = false;
+  NetworkEventsObserver network_events_observer;
+  network_events_observer.SetReportingEnabled(true);
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  network_events_observer.SetOnEventObservedCallback(test_future.GetCallback());
+
+  service_client()->SetServiceProperty(kVpnServicePath, shill::kStateProperty,
+                                       base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(test_future.IsEmpty());
+
+  service_client()->SetServiceProperty(kWifiServicePath, shill::kStateProperty,
+                                       base::Value(shill::kStateIdle));
+
+  MetricData metric_data = test_future.Take();
+  EXPECT_THAT(metric_data.event_data().type(),
+              Eq(MetricEventType::NETWORK_STATE_CHANGE));
+  EXPECT_THAT(metric_data.telemetry_data()
+                  .networks_telemetry()
+                  .network_connection_change_event_data()
+                  .connection_state(),
+              Eq(NetworkConnectionState::NOT_CONNECTED));
+  EXPECT_TRUE(test_future.IsEmpty());
+}
+
+TEST_F(NetworkEventsObserverConnectionStateTest, NewVpnConnection) {
+  static constexpr char kNewVpnServicePath1[] = "new-vpn-path1";
+  static constexpr char kNewVpnGuid1[] = "new-vpn-guid1";
+  static constexpr char kNewVpnServicePath2[] = "new-vpn-path2";
+  static constexpr char kNewVpnGuid2[] = "new-vpn-guid2";
+
+  scoped_feature_list_.InitAndEnableFeature(
+      kEnableVpnConnectionStateEventsReporting);
 
   NetworkEventsObserver network_events_observer;
-  MetricData result_metric_data;
-  auto cb =
-      base::BindLambdaForTesting([&](MetricData) { event_reported = true; });
+  network_events_observer.SetReportingEnabled(true);
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  network_events_observer.SetOnEventObservedCallback(test_future.GetCallback());
 
-  network_events_observer.SetOnEventObservedCallback(std::move(cb));
-  network_events_observer.OnConnectionStateChanged(kVpnGuid,
-                                                   NetworkState::kNotConnected);
+  service_client()->AddService(kNewVpnServicePath1, kNewVpnGuid1, "new-name1",
+                               shill::kTypeVPN, shill::kStateIdle,
+                               /*visible=*/true);
+  base::RunLoop().RunUntilIdle();
 
-  ASSERT_FALSE(event_reported);
+  // New connection added in disconnected state, nothing is reported.
+  ASSERT_TRUE(test_future.IsEmpty());
 
-  network_events_observer.OnConnectionStateChanged(kWifiGuid,
-                                                   NetworkState::kNotConnected);
+  // Change new connection state.
+  service_client()->SetServiceProperty(kNewVpnServicePath1,
+                                       shill::kStateProperty,
+                                       base::Value(shill::kStateReady));
 
-  EXPECT_TRUE(event_reported);
+  MetricData metric_data1 = test_future.Take();
+  const auto& connection_event_data1 =
+      metric_data1.telemetry_data()
+          .networks_telemetry()
+          .network_connection_change_event_data();
+  EXPECT_THAT(metric_data1.event_data().type(),
+              Eq(MetricEventType::VPN_CONNECTION_STATE_CHANGE));
+  EXPECT_THAT(connection_event_data1.guid(), StrEq(kNewVpnGuid1));
+  EXPECT_THAT(connection_event_data1.connection_state(),
+              Eq(NetworkConnectionState::CONNECTED));
+
+  service_client()->AddService(kNewVpnServicePath2, kNewVpnGuid2, "new-name2",
+                               shill::kTypeVPN, shill::kStateAssociation,
+                               /*visible=*/true);
+
+  // New connection added in connecting state should be reported.
+  MetricData metric_data2 = test_future.Take();
+  const auto& connection_event_data2 =
+      metric_data2.telemetry_data()
+          .networks_telemetry()
+          .network_connection_change_event_data();
+  EXPECT_THAT(metric_data2.event_data().type(),
+              Eq(MetricEventType::VPN_CONNECTION_STATE_CHANGE));
+  EXPECT_THAT(connection_event_data2.guid(), StrEq(kNewVpnGuid2));
+  EXPECT_THAT(connection_event_data2.connection_state(),
+              Eq(NetworkConnectionState::CONNECTING));
 }
 
 TEST_F(NetworkEventsObserverConnectionStateTest, TetherConnection) {
@@ -421,104 +502,141 @@
       /*disabled_features=*/
       {});
 
-  bool event_reported = false;
-
   NetworkEventsObserver network_events_observer;
-  MetricData result_metric_data;
-  auto cb =
-      base::BindLambdaForTesting([&](MetricData) { event_reported = true; });
+  network_events_observer.SetReportingEnabled(true);
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  network_events_observer.SetOnEventObservedCallback(test_future.GetCallback());
 
-  network_events_observer.SetOnEventObservedCallback(std::move(cb));
-  network_events_observer.OnConnectionStateChanged(kTetherGuid,
-                                                   NetworkState::kConnected);
+  service_client()->SetServiceProperty(kTetherServicePath,
+                                       shill::kStateProperty,
+                                       base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(event_reported);
+  ASSERT_TRUE(test_future.IsEmpty());
 }
 
-TEST_F(NetworkEventsObserverConnectionStateTest, InvalidGuid) {
-  scoped_feature_list_.InitWithFeatures(
-      /*enabled_features=*/{kEnableNetworkConnectionStateEventsReporting,
-                            kEnableVpnConnectionStateEventsReporting},
-      /*disabled_features=*/{});
+TEST_F(NetworkEventsObserverConnectionStateTest, WifiPortal) {
+  static constexpr char kNewWifiServicePath[] = "new-wifi-path";
+  static constexpr char kNewWifiGuid[] = "new-wifi-guid";
+
+  scoped_feature_list_.InitAndEnableFeature(
+      kEnableNetworkConnectionStateEventsReporting);
 
   NetworkEventsObserver network_events_observer;
-  bool event_reported = false;
-  auto cb =
-      base::BindLambdaForTesting([&](MetricData) { event_reported = true; });
+  network_events_observer.SetReportingEnabled(true);
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  network_events_observer.SetOnEventObservedCallback(test_future.GetCallback());
 
-  network_events_observer.SetOnEventObservedCallback(std::move(cb));
-  network_events_observer.SetReportingEnabled(/*is_enabled=*/true);
-  network_events_observer.OnConnectionStateChanged("invalid_guid",
-                                                   NetworkState::kOnline);
+  service_client()->AddService(kNewWifiServicePath, kNewWifiGuid, "new-name",
+                               shill::kTypeWifi, shill::kStateRedirectFound,
+                               /*visible=*/true);
 
-  ASSERT_FALSE(event_reported);
+  MetricData metric_data = test_future.Take();
+  const auto& connection_event_data =
+      metric_data.telemetry_data()
+          .networks_telemetry()
+          .network_connection_change_event_data();
+  EXPECT_THAT(metric_data.event_data().type(),
+              Eq(MetricEventType::NETWORK_STATE_CHANGE));
+  EXPECT_THAT(connection_event_data.guid(), Eq((kNewWifiGuid)));
+  EXPECT_THAT(connection_event_data.connection_state(),
+              Eq(NetworkConnectionState::PORTAL));
 }
 
 TEST_P(NetworkEventsObserverConnectionStateTest, Default) {
   const NetworkConnectionStateTestCase& test_case = GetParam();
-  bool event_reported = false;
+  static constexpr char kNewWifiServicePath[] = "new-wifi-path";
+  static constexpr char kNewWifiGuid[] = "new-wifi-guid";
 
   scoped_feature_list_.InitWithFeatures(
       /*enabled_features=*/{kEnableNetworkConnectionStateEventsReporting,
                             kEnableVpnConnectionStateEventsReporting},
       /*disabled_features=*/{});
 
+  service_client()->AddService(kNewWifiServicePath, kNewWifiGuid, "new-name",
+                               shill::kTypeWifi, test_case.other_state,
+                               /*visible=*/true);
+  base::RunLoop().RunUntilIdle();
+
   NetworkEventsObserver network_events_observer;
-  MetricData result_metric_data;
-  auto cb = base::BindLambdaForTesting([&](MetricData metric_data) {
-    event_reported = true;
-    result_metric_data = std::move(metric_data);
-  });
+  network_events_observer.SetReportingEnabled(true);
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  network_events_observer.SetOnEventObservedCallback(test_future.GetCallback());
 
-  network_events_observer.SetOnEventObservedCallback(std::move(cb));
-  network_events_observer.OnConnectionStateChanged(test_case.guid,
-                                                   test_case.input_state);
+  service_client()->SetServiceProperty(test_case.service_path,
+                                       shill::kStateProperty,
+                                       base::Value(test_case.input_state));
+  {
+    MetricData metric_data = test_future.Take();
+    const auto& connection_event_data =
+        metric_data.telemetry_data()
+            .networks_telemetry()
+            .network_connection_change_event_data();
+    EXPECT_THAT(metric_data.event_data().type(),
+                Eq(test_case.expected_event_type));
+    EXPECT_THAT(connection_event_data.guid(), Eq(test_case.guid));
+    EXPECT_THAT(connection_event_data.connection_state(),
+                Eq(test_case.expected_state));
+  }
 
-  ASSERT_TRUE(event_reported);
-  ASSERT_TRUE(result_metric_data.has_event_data());
-  EXPECT_THAT(result_metric_data.event_data().type(),
-              Eq(test_case.expected_event_type));
-  ASSERT_TRUE(result_metric_data.has_telemetry_data());
-  ASSERT_TRUE(result_metric_data.telemetry_data().has_networks_telemetry());
-  ASSERT_TRUE(result_metric_data.telemetry_data()
-                  .networks_telemetry()
-                  .has_network_connection_change_event_data());
-  EXPECT_THAT(result_metric_data.telemetry_data()
-                  .networks_telemetry()
-                  .network_connection_change_event_data()
-                  .guid(),
-              Eq(test_case.guid));
-  EXPECT_THAT(result_metric_data.telemetry_data()
-                  .networks_telemetry()
-                  .network_connection_change_event_data()
-                  .connection_state(),
-              Eq(test_case.expected_state));
-
-  // Same event with different guid should be reported.
-  event_reported = false;
-  network_events_observer.OnConnectionStateChanged(kWifiIdleGuid,
-                                                   test_case.input_state);
-  ASSERT_TRUE(event_reported);
+  // Same event for different service should be reported.
+  service_client()->SetServiceProperty(kNewWifiServicePath,
+                                       shill::kStateProperty,
+                                       base::Value(test_case.input_state));
+  {
+    MetricData metric_data = test_future.Take();
+    const auto& connection_event_data =
+        metric_data.telemetry_data()
+            .networks_telemetry()
+            .network_connection_change_event_data();
+    EXPECT_THAT(metric_data.event_data().type(),
+                MetricEventType::NETWORK_STATE_CHANGE);
+    EXPECT_THAT(connection_event_data.guid(), Eq(kNewWifiGuid));
+    EXPECT_THAT(connection_event_data.connection_state(),
+                Eq(test_case.expected_state));
+  }
 
   // Duplicate events should not be reported
-  event_reported = false;
-  network_events_observer.OnConnectionStateChanged(test_case.guid,
-                                                   test_case.input_state);
-  ASSERT_FALSE(event_reported);
+  service_client()->SetServiceProperty(test_case.service_path,
+                                       shill::kStateProperty,
+                                       base::Value(test_case.input_state));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(test_future.IsEmpty());
 
   // Different event for same network should be reported.
-  network_events_observer.OnConnectionStateChanged(test_case.guid,
-                                                   test_case.other_state);
-  ASSERT_TRUE(event_reported);
+  service_client()->SetServiceProperty(test_case.service_path,
+                                       shill::kStateProperty,
+                                       base::Value(test_case.other_state));
+  {
+    MetricData metric_data = test_future.Take();
+    const auto& connection_event_data =
+        metric_data.telemetry_data()
+            .networks_telemetry()
+            .network_connection_change_event_data();
+    EXPECT_THAT(metric_data.event_data().type(),
+                Eq(test_case.expected_event_type));
+    EXPECT_THAT(connection_event_data.guid(), Eq(test_case.guid));
+    EXPECT_THAT(connection_event_data.connection_state(),
+                Eq(test_case.other_expected_state));
+  }
 
-  // Same event with same guid should be reported if reporting state changed
-  // from disabled to enabled.
+  // Reporting disabled, no events should be reported.
   network_events_observer.SetReportingEnabled(/*is_enabled=*/false);
+  service_client()->SetServiceProperty(test_case.service_path,
+                                       shill::kStateProperty,
+                                       base::Value(test_case.input_state));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(test_future.IsEmpty());
+
+  // Same last network reported event should be reported if reporting state
+  // changed from disabled to enabled.
   network_events_observer.SetReportingEnabled(/*is_enabled=*/true);
-  event_reported = false;
-  network_events_observer.OnConnectionStateChanged(test_case.guid,
-                                                   test_case.other_state);
-  ASSERT_TRUE(event_reported);
+  service_client()->SetServiceProperty(test_case.service_path,
+                                       shill::kStateProperty,
+                                       base::Value(test_case.other_state));
+
+  EXPECT_TRUE(test_future.Wait());
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -526,32 +644,31 @@
     NetworkEventsObserverConnectionStateTest,
     ::testing::ValuesIn<NetworkConnectionStateTestCase>(
         {{.test_name = "WifiOnline",
+          .service_path = kWifiServicePath,
           .guid = kWifiGuid,
-          .input_state = NetworkState::kOnline,
-          .other_state = NetworkState::kNotConnected,
+          .input_state = shill::kStateOnline,
           .expected_event_type = MetricEventType::NETWORK_STATE_CHANGE,
           .expected_state = NetworkConnectionState::ONLINE},
          {.test_name = "CellularConnected",
+          .service_path = kCellularServicePath,
           .guid = kCellularGuid,
-          .input_state = NetworkState::kConnected,
+          .input_state = shill::kStateReady,
           .expected_event_type = MetricEventType::NETWORK_STATE_CHANGE,
           .expected_state = NetworkConnectionState::CONNECTED},
-         {.test_name = "WifiPortal",
-          .guid = kWifiGuid,
-          .input_state = NetworkState::kPortal,
-          .expected_event_type = MetricEventType::NETWORK_STATE_CHANGE,
-          .expected_state = NetworkConnectionState::PORTAL},
          {.test_name = "VpnConnecting",
+          .service_path = kVpnServicePath,
           .guid = kVpnGuid,
-          .input_state = NetworkState::kConnecting,
+          .input_state = shill::kStateAssociation,
           .expected_event_type = MetricEventType::VPN_CONNECTION_STATE_CHANGE,
           .expected_state = NetworkConnectionState::CONNECTING},
          {.test_name = "VpnNotConnected",
+          .service_path = kVpnServicePath,
           .guid = kVpnGuid,
-          .input_state = NetworkState::kNotConnected,
-          .other_state = NetworkState::kConnecting,
+          .input_state = shill::kStateIdle,
+          .other_state = shill::kStateConfiguration,
           .expected_event_type = MetricEventType::VPN_CONNECTION_STATE_CHANGE,
-          .expected_state = NetworkConnectionState::NOT_CONNECTED}}),
+          .expected_state = NetworkConnectionState::NOT_CONNECTED,
+          .other_expected_state = NetworkConnectionState::CONNECTING}}),
     [](const testing::TestParamInfo<
         NetworkEventsObserverConnectionStateTest::ParamType>& info) {
       return info.param.test_name;
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.cc
index 93d324e..75ffe90 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.cc
@@ -76,28 +76,6 @@
   return interface_info_it->Clone();
 }
 
-NetworkConnectionState GetNetworkConnectionState(
-    const ash::NetworkState* network) {
-  if (network->IsConnectedState()) {
-    auto portal_state = network->GetPortalState();
-    switch (portal_state) {
-      case ash::NetworkState::PortalState::kUnknown:
-        return NetworkConnectionState::CONNECTED;
-      case ash::NetworkState::PortalState::kOnline:
-        return NetworkConnectionState::ONLINE;
-      case ash::NetworkState::PortalState::kPortalSuspected:
-      case ash::NetworkState::PortalState::kPortal:
-      case ash::NetworkState::PortalState::kProxyAuthRequired:
-      case ash::NetworkState::PortalState::kNoInternet:
-        return NetworkConnectionState::PORTAL;
-    }
-  }
-  if (network->IsConnectingState()) {
-    return NetworkConnectionState::CONNECTING;
-  }
-  return NetworkConnectionState::NOT_CONNECTED;
-}
-
 NetworkType GetNetworkType(const ash::NetworkTypePattern& type) {
   if (type.Equals(ash::NetworkTypePattern::Cellular())) {
     return NetworkType::CELLULAR;
@@ -120,6 +98,29 @@
 
 }  // namespace
 
+// static
+NetworkConnectionState NetworkTelemetrySampler::GetNetworkConnectionState(
+    const ash::NetworkState* network) {
+  if (network->IsConnectedState()) {
+    auto portal_state = network->GetPortalState();
+    switch (portal_state) {
+      case ash::NetworkState::PortalState::kUnknown:
+        return NetworkConnectionState::CONNECTED;
+      case ash::NetworkState::PortalState::kOnline:
+        return NetworkConnectionState::ONLINE;
+      case ash::NetworkState::PortalState::kPortalSuspected:
+      case ash::NetworkState::PortalState::kPortal:
+      case ash::NetworkState::PortalState::kProxyAuthRequired:
+      case ash::NetworkState::PortalState::kNoInternet:
+        return NetworkConnectionState::PORTAL;
+    }
+  }
+  if (network->IsConnectingState()) {
+    return NetworkConnectionState::CONNECTING;
+  }
+  return NetworkConnectionState::NOT_CONNECTED;
+}
+
 NetworkTelemetrySampler::NetworkTelemetrySampler() = default;
 
 NetworkTelemetrySampler::~NetworkTelemetrySampler() = default;
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.h b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.h
index b3085e78..2070c8b 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler.h
@@ -7,8 +7,10 @@
 
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
+#include "chromeos/ash/components/network/network_state.h"
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
 #include "components/reporting/metrics/sampler.h"
+#include "components/reporting/proto/synced/metric_data.pb.h"
 
 namespace reporting {
 
@@ -16,6 +18,9 @@
 // and connections states.
 class NetworkTelemetrySampler : public Sampler {
  public:
+  static NetworkConnectionState GetNetworkConnectionState(
+      const ash::NetworkState* network);
+
   NetworkTelemetrySampler();
 
   NetworkTelemetrySampler(const NetworkTelemetrySampler&) = delete;
diff --git a/chrome/browser/ash/policy/reporting/os_updates/os_updates_reporter_browsertest.cc b/chrome/browser/ash/policy/reporting/os_updates/os_updates_reporter_browsertest.cc
index 7c4f93e..6b099ff 100644
--- a/chrome/browser/ash/policy/reporting/os_updates/os_updates_reporter_browsertest.cc
+++ b/chrome/browser/ash/policy/reporting/os_updates/os_updates_reporter_browsertest.cc
@@ -7,12 +7,12 @@
 #include "ash/constants/ash_switches.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/test/user_policy_mixin.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
 #include "chrome/browser/policy/messaging_layer/proto/synced/os_events.pb.h"
diff --git a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.cc
similarity index 97%
rename from chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc
rename to chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.cc
index c370891..37c6760 100644
--- a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc
+++ b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.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/ash/login/test/embedded_policy_test_server_mixin.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 
 #include <string>
 #include <utility>
@@ -11,9 +11,9 @@
 #include "base/time/time.h"
 #include "base/uuid.h"
 #include "base/values.h"
-#include "chrome/browser/ash/login/test/policy_test_server_constants.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h"
+#include "chrome/browser/ash/policy/test_support/policy_test_server_constants.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/test/base/fake_gaia_mixin.h"
@@ -206,8 +206,9 @@
       },
       &keys, loop.QuitClosure()));
   loop.Run();
-  if (keys.empty())
+  if (keys.empty()) {
     return false;
+  }
 
   policy::ClientStorage::ClientInfo client_info;
   client_info.device_token = "dm_token";
diff --git a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h
similarity index 95%
rename from chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h
rename to chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h
index d873487..30d013af 100644
--- a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h
+++ b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.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 CHROME_BROWSER_ASH_LOGIN_TEST_EMBEDDED_POLICY_TEST_SERVER_MIXIN_H_
-#define CHROME_BROWSER_ASH_LOGIN_TEST_EMBEDDED_POLICY_TEST_SERVER_MIXIN_H_
+#ifndef CHROME_BROWSER_ASH_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_MIXIN_H_
+#define CHROME_BROWSER_ASH_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_MIXIN_H_
 
 #include <initializer_list>
 #include <memory>
@@ -17,7 +17,6 @@
 #include "components/policy/proto/chrome_device_policy.pb.h"
 #include "components/policy/proto/cloud_policy.pb.h"
 #include "components/policy/proto/device_management_backend.pb.h"
-#include "net/http/http_status_code.h"
 
 namespace policy {
 class EmbeddedPolicyTestServer;
@@ -155,4 +154,4 @@
 
 }  // namespace ash
 
-#endif  // CHROME_BROWSER_ASH_LOGIN_TEST_EMBEDDED_POLICY_TEST_SERVER_MIXIN_H_
+#endif  // CHROME_BROWSER_ASH_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_MIXIN_H_
diff --git a/chrome/browser/ash/policy/test_support/policy_test_server_constants.h b/chrome/browser/ash/policy/test_support/policy_test_server_constants.h
new file mode 100644
index 0000000..73b35f3a8
--- /dev/null
+++ b/chrome/browser/ash/policy/test_support/policy_test_server_constants.h
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_POLICY_TEST_SUPPORT_POLICY_TEST_SERVER_CONSTANTS_H_
+#define CHROME_BROWSER_ASH_POLICY_TEST_SUPPORT_POLICY_TEST_SERVER_CONSTANTS_H_
+
+namespace ash::test {
+
+// Test constants used during enrollment wherever appropriate.
+constexpr char kTestDomain[] = "test-domain.com";
+constexpr char kTestRlzBrandCodeKey[] = "TEST";
+constexpr char kTestSerialNumber[] = "111111";
+constexpr char kTestHardwareClass[] = "hw";
+
+}  // namespace ash::test
+
+#endif  // CHROME_BROWSER_ASH_POLICY_TEST_SUPPORT_POLICY_TEST_SERVER_CONSTANTS_H_
diff --git a/chrome/browser/ash/printing/cups_print_job_manager_impl.cc b/chrome/browser/ash/printing/cups_print_job_manager_impl.cc
index 4c7f4cf9..0ea5c5a8 100644
--- a/chrome/browser/ash/printing/cups_print_job_manager_impl.cc
+++ b/chrome/browser/ash/printing/cups_print_job_manager_impl.cc
@@ -495,6 +495,8 @@
         return "Stopped";
       case StatusReason::kTrayMissing:
         return "TrayMissing";
+      case StatusReason::kExpiredCertificate:
+        return "ExpiredCertificate";
     }
   }
 
diff --git a/chrome/browser/ash/printing/cups_print_job_notification.cc b/chrome/browser/ash/printing/cups_print_job_notification.cc
index 4c3b7c0..f6ba95c9 100644
--- a/chrome/browser/ash/printing/cups_print_job_notification.cc
+++ b/chrome/browser/ash/printing/cups_print_job_notification.cc
@@ -47,6 +47,9 @@
     case PrinterErrorCode::CLIENT_UNAUTHORIZED:
       return l10n_util::GetStringUTF16(
           IDS_PRINT_JOB_AUTHORIZATION_ERROR_NOTIFICATION_TITLE);
+    case PrinterErrorCode::EXPIRED_CERTIFICATE:
+      return l10n_util::GetStringUTF16(
+          IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE);
     default:
       return l10n_util::GetStringUTF16(IDS_PRINT_JOB_ERROR_NOTIFICATION_TITLE);
   }
@@ -81,6 +84,9 @@
     case PrinterErrorCode::STOPPED:
       return l10n_util::GetStringUTF16(
           IDS_PRINT_JOB_STOPPED_NOTIFICATION_TITLE);
+    case PrinterErrorCode::EXPIRED_CERTIFICATE:
+      return l10n_util::GetStringUTF16(
+          IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE);
     default:
       return l10n_util::GetStringUTF16(IDS_PRINT_JOB_ERROR_NOTIFICATION_TITLE);
   }
diff --git a/chrome/browser/ash/printing/cups_printer_status_creator.cc b/chrome/browser/ash/printing/cups_printer_status_creator.cc
index 56789065..45e765e 100644
--- a/chrome/browser/ash/printing/cups_printer_status_creator.cc
+++ b/chrome/browser/ash/printing/cups_printer_status_creator.cc
@@ -87,6 +87,8 @@
       return CupsReason::kTrayMissing;
     case ReasonFromPrinter::kUnknownReason:
       return CupsReason::kUnknownReason;
+    case ReasonFromPrinter::kCupsPkiExpired:
+      return CupsReason::kExpiredCertificate;
   }
 }
 
diff --git a/chrome/browser/ash/printing/history/print_job_info.proto b/chrome/browser/ash/printing/history/print_job_info.proto
index 0e083944..2f91f37 100644
--- a/chrome/browser/ash/printing/history/print_job_info.proto
+++ b/chrome/browser/ash/printing/history/print_job_info.proto
@@ -118,6 +118,8 @@
     UNKNOWN_ERROR = 10;
     // The authorization with the printer has failed.
     CLIENT_UNAUTHORIZED = 11;
+    // The SSL certificate in the printer is expired.
+    EXPIRED_CERTIFICATE = 12;
   }
 
   // The ID of the job.
diff --git a/chrome/browser/ash/printing/history/print_job_info_proto_conversions.cc b/chrome/browser/ash/printing/history/print_job_info_proto_conversions.cc
index 9932773..b4549b45 100644
--- a/chrome/browser/ash/printing/history/print_job_info_proto_conversions.cc
+++ b/chrome/browser/ash/printing/history/print_job_info_proto_conversions.cc
@@ -123,6 +123,8 @@
       return proto::PrintJobInfo_PrinterErrorCode_UNKNOWN_ERROR;
     case PrinterErrorCode::CLIENT_UNAUTHORIZED:
       return proto::PrintJobInfo_PrinterErrorCode_CLIENT_UNAUTHORIZED;
+    case PrinterErrorCode::EXPIRED_CERTIFICATE:
+      return proto::PrintJobInfo_PrinterErrorCode_EXPIRED_CERTIFICATE;
     default:
       // Be sure to update the above case statements whenever a new printer
       // error is introduced.
diff --git a/chrome/browser/ash/printing/print_management/print_job_info_mojom_conversions.cc b/chrome/browser/ash/printing/print_management/print_job_info_mojom_conversions.cc
index b52ab8e..ae5329d 100644
--- a/chrome/browser/ash/printing/print_management/print_job_info_mojom_conversions.cc
+++ b/chrome/browser/ash/printing/print_management/print_job_info_mojom_conversions.cc
@@ -88,6 +88,8 @@
       return mojom::PrinterErrorCode::kUnknownError;
     case proto::PrintJobInfo_PrinterErrorCode_CLIENT_UNAUTHORIZED:
       return mojom::PrinterErrorCode::kClientUnauthorized;
+    case proto::PrintJobInfo_PrinterErrorCode_EXPIRED_CERTIFICATE:
+      return mojom::PrinterErrorCode::kExpiredCertificate;
     case proto::
         PrintJobInfo_PrinterErrorCode_PrintJobInfo_PrinterErrorCode_INT_MIN_SENTINEL_DO_NOT_USE_:
     case proto::
@@ -124,6 +126,8 @@
       return mojom::PrinterErrorCode::kUnknownError;
     case PrinterErrorCode::CLIENT_UNAUTHORIZED:
       return mojom::PrinterErrorCode::kClientUnauthorized;
+    case PrinterErrorCode::EXPIRED_CERTIFICATE:
+      return mojom::PrinterErrorCode::kExpiredCertificate;
   }
   return mojom::PrinterErrorCode::kUnknownError;
 }
diff --git a/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc b/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
index ae4a2db..5449a2a 100644
--- a/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
+++ b/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
@@ -36,10 +36,10 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
 #include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/remote_apps/id_generator.h"
 #include "chrome/browser/ash/remote_apps/remote_apps_manager_factory.h"
diff --git a/chrome/browser/ash/system/timezone_resolver_manager_browsertest.cc b/chrome/browser/ash/system/timezone_resolver_manager_browsertest.cc
index 11e60c8..1b9dde1 100644
--- a/chrome/browser/ash/system/timezone_resolver_manager_browsertest.cc
+++ b/chrome/browser/ash/system/timezone_resolver_manager_browsertest.cc
@@ -7,10 +7,10 @@
 #include "ash/constants/ash_switches.h"
 #include "chrome/browser/ash/login/login_manager_test.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/ui/user_adding_screen.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
 #include "chrome/browser/browser_process.h"
diff --git a/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc
index 602f437..539aaf54 100644
--- a/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc
@@ -142,8 +142,8 @@
   histogram_tester_.ExpectUniqueSample("ChromeOS.DiagnosticsUi.InitialScreen",
                                        0, 1);
 
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents, "chrome.send('recordNavigation', [0, 1]);"));
+  EXPECT_TRUE(content::ExecJs(web_contents,
+                              "chrome.send('recordNavigation', [0, 1]);"));
 
   chrome::CloseAllBrowsers();
 
@@ -161,14 +161,14 @@
                                        0, 1);
 
   // Simulate sending invalid navigation view value.
-  EXPECT_TRUE(content::ExecuteScript(
+  EXPECT_TRUE(content::ExecJs(
       web_contents, "chrome.send('recordNavigation', [1000, -550]);"));
-  EXPECT_TRUE(content::ExecuteScript(
+  EXPECT_TRUE(content::ExecJs(
       web_contents, "chrome.send('recordNavigation', ['1000', '-550']);"));
   EXPECT_TRUE(
-      content::ExecuteScript(web_contents, "chrome.send('recordNavigation');"));
-  EXPECT_TRUE(content::ExecuteScript(web_contents,
-                                     "chrome.send('recordNavigation', []);"));
+      content::ExecJs(web_contents, "chrome.send('recordNavigation');"));
+  EXPECT_TRUE(
+      content::ExecJs(web_contents, "chrome.send('recordNavigation', []);"));
 
   chrome::CloseAllBrowsers();
 
diff --git a/chrome/browser/autofill/manual_filling_controller.h b/chrome/browser/autofill/manual_filling_controller.h
index 4cd4983..2b39ec3 100644
--- a/chrome/browser/autofill/manual_filling_controller.h
+++ b/chrome/browser/autofill/manual_filling_controller.h
@@ -6,7 +6,9 @@
 #define CHROME_BROWSER_AUTOFILL_MANUAL_FILLING_CONTROLLER_H_
 
 #include "base/memory/weak_ptr.h"
+#include "base/types/strong_alias.h"
 #include "components/autofill/core/browser/ui/accessory_sheet_data.h"
+#include "components/autofill/core/browser/ui/accessory_sheet_enums.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom-forward.h"
 #include "components/autofill/core/common/unique_ids.h"
 #include "content/public/browser/web_contents_user_data.h"
@@ -44,6 +46,8 @@
     ADDRESS_FALLBACKS,
   };
 
+  using ShouldShowAction = base::StrongAlias<struct ShouldShowActionTag, bool>;
+
   ManualFillingController() = default;
 
   ManualFillingController(const ManualFillingController&) = delete;
@@ -94,12 +98,10 @@
   // E.g. after autofilling suggestions, or generating a password.
   virtual void Hide() = 0;
 
-  // Notifies the view that automatic password generation status changed.
-  //
-  // TODO(crbug.com/905669): This controller doesn't need to know about password
-  // generation. Generalize this to send to the UI the information that an
-  // action (given by an enum param) is available.
-  virtual void OnAutomaticGenerationStatusChanged(bool available) = 0;
+  // Notifies the view that availability of the given action changed.
+  virtual void OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction shouldShowAction,
+      autofill::AccessoryAction action) = 0;
 
   // Instructs the view to show the manual filling sheet for the given
   // |tab_type|.
diff --git a/chrome/browser/autofill/manual_filling_controller_impl.cc b/chrome/browser/autofill/manual_filling_controller_impl.cc
index 342d80f..774960b 100644
--- a/chrome/browser/autofill/manual_filling_controller_impl.cc
+++ b/chrome/browser/autofill/manual_filling_controller_impl.cc
@@ -104,6 +104,7 @@
     // These cases are sufficient as a reason for showing the fallback sheet.
     case AccessoryAction::USE_OTHER_PASSWORD:
     case AccessoryAction::GENERATE_PASSWORD_MANUAL:
+    case AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY:
       return true;
 
     case AccessoryAction::COUNT:
@@ -177,10 +178,11 @@
   FromWebContents(web_contents)->Initialize();
 }
 
-void ManualFillingControllerImpl::OnAutomaticGenerationStatusChanged(
-    bool available) {
+void ManualFillingControllerImpl::OnAccessoryActionAvailabilityChanged(
+    ShouldShowAction shouldShowAction,
+    autofill::AccessoryAction action) {
   DCHECK(view_);
-  view_->OnAutomaticGenerationStatusChanged(available);
+  view_->OnAccessoryActionAvailabilityChanged(shouldShowAction, action);
 }
 
 void ManualFillingControllerImpl::RefreshSuggestions(
@@ -501,6 +503,10 @@
     case AccessoryAction::MANAGE_CREDIT_CARDS:
       return cc_controller_.get();
     case AccessoryAction::AUTOFILL_SUGGESTION:
+    case AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY:
+      // TODO(crbug/1444418): Implement.
+      NOTIMPLEMENTED();
+      ABSL_FALLTHROUGH_INTENDED;
     case AccessoryAction::COUNT:
       NOTREACHED() << "Controller not defined for action: "
                    << static_cast<int>(action);
diff --git a/chrome/browser/autofill/manual_filling_controller_impl.h b/chrome/browser/autofill/manual_filling_controller_impl.h
index 351e9e8..f1d5dd3 100644
--- a/chrome/browser/autofill/manual_filling_controller_impl.h
+++ b/chrome/browser/autofill/manual_filling_controller_impl.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/autofill/accessory_controller.h"
 #include "chrome/browser/autofill/manual_filling_controller.h"
 #include "chrome/browser/autofill/manual_filling_view_interface.h"
+#include "components/autofill/core/browser/ui/accessory_sheet_enums.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
 #include "components/autofill/core/common/unique_ids.h"
 #include "content/public/browser/web_contents_user_data.h"
@@ -46,7 +47,9 @@
   void UpdateSourceAvailability(FillingSource source,
                                 bool has_suggestions) override;
   void Hide() override;
-  void OnAutomaticGenerationStatusChanged(bool available) override;
+  void OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction shouldShowAction,
+      autofill::AccessoryAction action) override;
   void ShowAccessorySheetTab(
       const autofill::AccessoryTabType& tab_type) override;
   void OnFillingTriggered(
diff --git a/chrome/browser/autofill/manual_filling_controller_impl_unittest.cc b/chrome/browser/autofill/manual_filling_controller_impl_unittest.cc
index 22f1fa7..770b1f39 100644
--- a/chrome/browser/autofill/manual_filling_controller_impl_unittest.cc
+++ b/chrome/browser/autofill/manual_filling_controller_impl_unittest.cc
@@ -52,6 +52,7 @@
 using FillingSource = ManualFillingController::FillingSource;
 using IsFillingSourceAvailable = AccessoryController::IsFillingSourceAvailable;
 using WaitForKeyboard = ManualFillingViewInterface::WaitForKeyboard;
+using ShouldShowAction = ManualFillingController::ShouldShowAction;
 
 AccessorySheetData empty_passwords_sheet() {
   constexpr char16_t kTitle[] = u"Example title";
@@ -404,12 +405,19 @@
                                          /*has_suggestions=*/false);
 }
 
-TEST_F(ManualFillingControllerLegacyTest, OnAutomaticGenerationStatusChanged) {
-  EXPECT_CALL(*view(), OnAutomaticGenerationStatusChanged(true));
-  controller()->OnAutomaticGenerationStatusChanged(true);
+TEST_F(ManualFillingControllerLegacyTest,
+       OnAccessoryActionAvailabilityChanged) {
+  EXPECT_CALL(*view(), OnAccessoryActionAvailabilityChanged(
+                           ShouldShowAction(true),
+                           AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
+  controller()->OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction(true), AccessoryAction::GENERATE_PASSWORD_AUTOMATIC);
 
-  EXPECT_CALL(*view(), OnAutomaticGenerationStatusChanged(false));
-  controller()->OnAutomaticGenerationStatusChanged(false);
+  EXPECT_CALL(*view(), OnAccessoryActionAvailabilityChanged(
+                           ShouldShowAction(false),
+                           AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
+  controller()->OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction(false), AccessoryAction::GENERATE_PASSWORD_AUTOMATIC);
 }
 
 TEST_F(ManualFillingControllerLegacyTest,
diff --git a/chrome/browser/autofill/manual_filling_view_interface.h b/chrome/browser/autofill/manual_filling_view_interface.h
index 106c953c..fcea6af 100644
--- a/chrome/browser/autofill/manual_filling_view_interface.h
+++ b/chrome/browser/autofill/manual_filling_view_interface.h
@@ -56,6 +56,7 @@
   };
 
   using WaitForKeyboard = base::StrongAlias<struct WaitForKeyboardTag, bool>;
+  using ShouldShowAction = base::StrongAlias<struct ShouldShowActionTag, bool>;
 
   virtual ~ManualFillingViewInterface() = default;
 
@@ -63,9 +64,10 @@
   // accessory sheet of the same type.
   virtual void OnItemsAvailable(autofill::AccessorySheetData data) = 0;
 
-  // Called when the generation action should be offered or rescinded
-  // in the keyboard accessory.
-  virtual void OnAutomaticGenerationStatusChanged(bool available) = 0;
+  // Called when a keyboard accessory action should be offered or rescinded.
+  virtual void OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction shouldShowAction,
+      autofill::AccessoryAction action) = 0;
 
   // Called to inform the view that the accessory sheet should be closed now.
   virtual void CloseAccessorySheet() = 0;
diff --git a/chrome/browser/autofill/mock_manual_filling_controller.h b/chrome/browser/autofill/mock_manual_filling_controller.h
index d6e6c65..b1aa83e 100644
--- a/chrome/browser/autofill/mock_manual_filling_controller.h
+++ b/chrome/browser/autofill/mock_manual_filling_controller.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_AUTOFILL_MOCK_MANUAL_FILLING_CONTROLLER_H_
 
 #include "chrome/browser/autofill/manual_filling_controller.h"
+#include "components/autofill/core/browser/ui/accessory_sheet_enums.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 class MockManualFillingController
@@ -20,27 +21,46 @@
 
   ~MockManualFillingController() override;
 
-  MOCK_METHOD1(RefreshSuggestions, void(const autofill::AccessorySheetData&));
-  MOCK_METHOD2(NotifyFocusedInputChanged,
-               void(autofill::FieldRendererId,
-                    autofill::mojom::FocusedFieldType));
-  MOCK_METHOD2(UpdateSourceAvailability,
-               void(ManualFillingController::FillingSource, bool));
-  MOCK_METHOD0(Hide, void());
-  MOCK_METHOD1(OnAutomaticGenerationStatusChanged, void(bool));
-  MOCK_METHOD1(ShowAccessorySheetTab, void(const autofill::AccessoryTabType&));
-  MOCK_METHOD2(OnFillingTriggered,
-               void(autofill::AccessoryTabType type,
-                    const autofill::AccessorySheetField&));
-  MOCK_CONST_METHOD1(OnOptionSelected,
-                     void(autofill::AccessoryAction selected_action));
-  MOCK_CONST_METHOD2(OnToggleChanged,
-                     void(autofill::AccessoryAction toggled_action,
-                          bool enabled));
-  MOCK_METHOD2(RequestAccessorySheet,
-               void(autofill::AccessoryTabType,
-                    base::OnceCallback<void(autofill::AccessorySheetData)>));
-  MOCK_CONST_METHOD0(container_view, gfx::NativeView());
+  MOCK_METHOD(void,
+              RefreshSuggestions,
+              (const autofill::AccessorySheetData&),
+              (override));
+  MOCK_METHOD((void),
+              NotifyFocusedInputChanged,
+              (autofill::FieldRendererId, autofill::mojom::FocusedFieldType),
+              (override));
+  MOCK_METHOD((void),
+              UpdateSourceAvailability,
+              (ManualFillingController::FillingSource, bool),
+              (override));
+  MOCK_METHOD((void), Hide, (), (override));
+  MOCK_METHOD((void),
+              OnAccessoryActionAvailabilityChanged,
+              (ShouldShowAction, autofill::AccessoryAction),
+              (override));
+  MOCK_METHOD(void,
+              ShowAccessorySheetTab,
+              (const autofill::AccessoryTabType&),
+              (override));
+  MOCK_METHOD((void),
+              OnFillingTriggered,
+              (autofill::AccessoryTabType type,
+               const autofill::AccessorySheetField&),
+              (override));
+  MOCK_METHOD((void),
+              OnOptionSelected,
+              (autofill::AccessoryAction selected_action),
+              (const, override));
+  MOCK_METHOD((void),
+              OnToggleChanged,
+              (autofill::AccessoryAction toggled_action, bool enabled),
+              (const, override));
+  MOCK_METHOD((void),
+              RequestAccessorySheet,
+              (autofill::AccessoryTabType,
+               base::OnceCallback<void(autofill::AccessorySheetData)>),
+              (override));
+  MOCK_METHOD((gfx::NativeView), container_view, (), (const, override));
 };
 
 #endif  // CHROME_BROWSER_AUTOFILL_MOCK_MANUAL_FILLING_CONTROLLER_H_
diff --git a/chrome/browser/autofill/mock_manual_filling_view.h b/chrome/browser/autofill/mock_manual_filling_view.h
index 4a7c5cd..575c5c6 100644
--- a/chrome/browser/autofill/mock_manual_filling_view.h
+++ b/chrome/browser/autofill/mock_manual_filling_view.h
@@ -19,13 +19,22 @@
 
   ~MockManualFillingView() override;
 
-  MOCK_METHOD1(OnItemsAvailable, void(autofill::AccessorySheetData));
-  MOCK_METHOD1(OnAutomaticGenerationStatusChanged, void(bool));
-  MOCK_METHOD0(CloseAccessorySheet, void());
-  MOCK_METHOD0(SwapSheetWithKeyboard, void());
-  MOCK_METHOD1(Show, void(WaitForKeyboard));
-  MOCK_METHOD0(Hide, void());
-  MOCK_METHOD1(ShowAccessorySheetTab, void(const autofill::AccessoryTabType&));
+  MOCK_METHOD((void),
+              OnItemsAvailable,
+              (autofill::AccessorySheetData),
+              (override));
+  MOCK_METHOD((void),
+              OnAccessoryActionAvailabilityChanged,
+              (ShouldShowAction, autofill::AccessoryAction),
+              (override));
+  MOCK_METHOD((void), CloseAccessorySheet, (), (override));
+  MOCK_METHOD((void), SwapSheetWithKeyboard, (), (override));
+  MOCK_METHOD((void), Show, (WaitForKeyboard), (override));
+  MOCK_METHOD((void), Hide, (), (override));
+  MOCK_METHOD((void),
+              ShowAccessorySheetTab,
+              (const autofill::AccessoryTabType&),
+              (override));
 };
 
 #endif  // CHROME_BROWSER_AUTOFILL_MOCK_MANUAL_FILLING_VIEW_H_
diff --git a/chrome/browser/browsing_data/browsing_data_model_browsertest.cc b/chrome/browser/browsing_data/browsing_data_model_browsertest.cc
index 831fdda..63f90f2 100644
--- a/chrome/browser/browsing_data/browsing_data_model_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_model_browsertest.cc
@@ -704,4 +704,51 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_P(BrowsingDataModelBrowserTest,
+                       LocalStorageHandledCorrectly) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      https_test_server()->GetURL(kTestHost, "/browsing_data/site_data.html")));
+  // Ensure that there isn't any data fetched.
+  std::unique_ptr<BrowsingDataModel> browsing_data_model =
+      BuildBrowsingDataModel();
+  ValidateBrowsingDataEntries(browsing_data_model.get(), {});
+  ASSERT_EQ(browsing_data_model->size(), 0u);
+
+  SetDataForType("LocalStorage", web_contents());
+
+  // Ensure that local storage is fetched
+  browsing_data_model = BuildBrowsingDataModel();
+
+  bool is_cookies_tree_model_deprecated = GetParam();
+  if (is_cookies_tree_model_deprecated) {
+    // Validate that local storage is fetched to browsing data model.
+    url::Origin testOrigin = https_test_server()->GetOrigin(kTestHost);
+    auto data_key = blink::StorageKey::CreateFirstParty(testOrigin);
+    ValidateBrowsingDataEntriesIgnoreUsage(
+        browsing_data_model.get(),
+        {{kTestHost,
+          data_key,
+          {BrowsingDataModel::StorageType::kLocalStorage,
+           /*storage_size=*/0, /*cookie_count=*/0}}});
+    ASSERT_EQ(browsing_data_model->size(), 1u);
+
+    // Remove local storage entry.
+    {
+      base::RunLoop run_loop;
+      browsing_data_model.get()->RemoveBrowsingData(kTestHost,
+                                                    run_loop.QuitClosure());
+      run_loop.Run();
+    }
+
+    // Rebuild Browsing Data Model and verify entries are empty.
+    browsing_data_model = BuildBrowsingDataModel();
+    ValidateBrowsingDataEntries(browsing_data_model.get(), {});
+    ASSERT_EQ(browsing_data_model->size(), 0u);
+  } else {
+    ValidateBrowsingDataEntries(browsing_data_model.get(), {});
+    ASSERT_EQ(browsing_data_model->size(), 0u);
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(All, BrowsingDataModelBrowserTest, ::testing::Bool());
diff --git a/chrome/browser/browsing_data/local_data_container.cc b/chrome/browser/browsing_data/local_data_container.cc
index cf6c830..74eda9eca 100644
--- a/chrome/browser/browsing_data/local_data_container.cc
+++ b/chrome/browser/browsing_data/local_data_container.cc
@@ -43,8 +43,7 @@
         base::MakeRefCounted<browsing_data::CookieHelper>(
             storage_partition, is_cookie_deletion_disabled_callback),
         /*database_helper=*/nullptr,
-        base::MakeRefCounted<browsing_data::LocalStorageHelper>(
-            storage_partition),
+        /*local_storage_helper=*/nullptr,
         /*session_storage_helper=*/nullptr,
         /*indexed_db_helper=*/nullptr,
         /*file_system_helper=*/nullptr,
diff --git a/chrome/browser/chained_back_navigation_tracker.h b/chrome/browser/chained_back_navigation_tracker.h
index 7fc0e8fc..adf23d36 100644
--- a/chrome/browser/chained_back_navigation_tracker.h
+++ b/chrome/browser/chained_back_navigation_tracker.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_CHAINED_BACK_NAVIGATION_TRACKER_H_
 #define CHROME_BROWSER_CHAINED_BACK_NAVIGATION_TRACKER_H_
 
+#include "base/gtest_prod_util.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
diff --git a/chrome/browser/chained_back_navigation_tracker_browsertest.cc b/chrome/browser/chained_back_navigation_tracker_browsertest.cc
index a15207f8..515543d 100644
--- a/chrome/browser/chained_back_navigation_tracker_browsertest.cc
+++ b/chrome/browser/chained_back_navigation_tracker_browsertest.cc
@@ -62,12 +62,12 @@
   ASSERT_EQ(1u, tracker->chained_back_navigation_count_);
 
   // Create a subframe and append it to the document.
-  ASSERT_TRUE(ExecuteScript(
-      web_contents(),
-      content::JsReplace("let frame = document.createElement('iframe');"
-                         "frame.src = $1;"
-                         "document.body.appendChild(frame);",
-                         url_b)));
+  ASSERT_TRUE(
+      ExecJs(web_contents(),
+             content::JsReplace("let frame = document.createElement('iframe');"
+                                "frame.src = $1;"
+                                "document.body.appendChild(frame);",
+                                url_b)));
   ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
   content::RenderFrameHost* subframe_host =
       ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 5fdef60..29958cf 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -14,6 +14,7 @@
 
 #include "base/containers/flat_map.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_piece_forward.h"
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/service_worker_lifetime_manager.h b/chrome/browser/chromeos/extensions/file_system_provider/service_worker_lifetime_manager.h
index f63a94a..9c7f7764 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/service_worker_lifetime_manager.h
+++ b/chrome/browser/chromeos/extensions/file_system_provider/service_worker_lifetime_manager.h
@@ -9,6 +9,7 @@
 #include <set>
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/singleton.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/cleanup/browser_cleanup_handler_browsertest.cc b/chrome/browser/chromeos/extensions/login_screen/login/cleanup/browser_cleanup_handler_browsertest.cc
index 29455b2..bcdde54 100644
--- a/chrome/browser/chromeos/extensions/login_screen/login/cleanup/browser_cleanup_handler_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/login_screen/login/cleanup/browser_cleanup_handler_browsertest.cc
@@ -10,9 +10,9 @@
 #include "ash/constants/ash_switches.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/test/base/in_process_browser_test.h"
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc b/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc
index b705889..247ad32 100644
--- a/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc
@@ -9,11 +9,11 @@
 
 #include "ash/constants/ash_switches.h"
 #include "base/path_service.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/component_loader.h"
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc b/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc
index 489c8b24..6d726045 100644
--- a/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc
+++ b/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc
@@ -13,9 +13,9 @@
 #include "chrome/browser/ash/login/existing_user_controller.h"
 #include "chrome/browser/ash/login/lock/screen_locker.h"
 #include "chrome/browser/ash/login/lock/screen_locker_tester.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/extensions/login_screen/login_screen_apitest_base.h"
diff --git a/chrome/browser/chromeos/printing/printer_error_codes.cc b/chrome/browser/chromeos/printing/printer_error_codes.cc
index 05fca2e..b7d14de 100644
--- a/chrome/browser/chromeos/printing/printer_error_codes.cc
+++ b/chrome/browser/chromeos/printing/printer_error_codes.cc
@@ -54,6 +54,9 @@
 STATIC_ASSERT_ENUM(
     PrinterErrorCode::CLIENT_UNAUTHORIZED,
     printing::printing_manager::mojom::PrinterErrorCode::kClientUnauthorized);
+STATIC_ASSERT_ENUM(
+    PrinterErrorCode::EXPIRED_CERTIFICATE,
+    printing::printing_manager::mojom::PrinterErrorCode::kExpiredCertificate);
 }  // namespace
 
 using PrinterReason = ::printing::PrinterStatus::PrinterReason;
@@ -95,6 +98,8 @@
       case PrinterReason::Reason::kPaused:
       case PrinterReason::Reason::kMovingToPaused:
         return PrinterErrorCode::STOPPED;
+      case PrinterReason::Reason::kCupsPkiExpired:
+        return PrinterErrorCode::EXPIRED_CERTIFICATE;
       case PrinterReason::Reason::kMediaLow:
       case PrinterReason::Reason::kTonerLow:
       case PrinterReason::Reason::kDeveloperLow:
diff --git a/chrome/browser/chromeos/printing/printer_error_codes.h b/chrome/browser/chromeos/printing/printer_error_codes.h
index 8394576..44103bdc 100644
--- a/chrome/browser/chromeos/printing/printer_error_codes.h
+++ b/chrome/browser/chromeos/printing/printer_error_codes.h
@@ -25,6 +25,7 @@
   FILTER_FAILED,
   UNKNOWN_ERROR,
   CLIENT_UNAUTHORIZED,
+  EXPIRED_CERTIFICATE,
 };
 
 // Extracts an PrinterErrorCode from PrinterStatus#reasons. Returns NO_ERROR if
diff --git a/chrome/browser/chromeos/reporting/metric_default_utils.h b/chrome/browser/chromeos/reporting/metric_default_utils.h
index 89b4d8b..87ba8e0 100644
--- a/chrome/browser/chromeos/reporting/metric_default_utils.h
+++ b/chrome/browser/chromeos/reporting/metric_default_utils.h
@@ -49,6 +49,11 @@
 // Minimum usage time threshold for app usage reporting.
 constexpr base::TimeDelta kMinimumAppUsageTime = base::Milliseconds(1);
 
+// Default value that controls app inventory reporting. Set to false even though
+// the corresponding user policy is a list type to signify reporting is
+// disallowed by default.
+constexpr bool kReportAppInventoryEnabledDefaultValue = false;
+
 // Default value for reporting device activity heartbeats.
 constexpr bool kDeviceActivityHeartbeatEnabledDefaultValue = false;
 
diff --git a/chrome/browser/companion/core/BUILD.gn b/chrome/browser/companion/core/BUILD.gn
index 014cec7..0090dca 100644
--- a/chrome/browser/companion/core/BUILD.gn
+++ b/chrome/browser/companion/core/BUILD.gn
@@ -23,6 +23,7 @@
     "mojom:mojo_bindings",
     "proto",
     "//base",
+    "//chrome/browser/ui/side_panel:side_panel_enums",
     "//components/prefs",
     "//components/unified_consent",
     "//content/public/browser",
@@ -47,6 +48,7 @@
     "proto",
     "//base",
     "//base/test:test_support",
+    "//chrome/browser/ui/side_panel:side_panel_enums",
     "//chrome/test:test_support",
     "//components/prefs",
     "//components/ukm:test_support",
diff --git a/chrome/browser/companion/core/companion_metrics_logger.cc b/chrome/browser/companion/core/companion_metrics_logger.cc
index eb9a3bca..30421a9 100644
--- a/chrome/browser/companion/core/companion_metrics_logger.cc
+++ b/chrome/browser/companion/core/companion_metrics_logger.cc
@@ -119,7 +119,8 @@
   FlushStats();
 }
 
-void CompanionMetricsLogger::RecordOpenTrigger(OpenTrigger open_trigger) {
+void CompanionMetricsLogger::RecordOpenTrigger(
+    absl::optional<SidePanelOpenTrigger> open_trigger) {
   open_trigger_ = open_trigger;
 }
 
diff --git a/chrome/browser/companion/core/companion_metrics_logger.h b/chrome/browser/companion/core/companion_metrics_logger.h
index 385c85e..16b235894 100644
--- a/chrome/browser/companion/core/companion_metrics_logger.h
+++ b/chrome/browser/companion/core/companion_metrics_logger.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/companion/core/mojom/companion.mojom.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "url/gurl.h"
 
@@ -36,24 +37,6 @@
   kClicked = 3,
 };
 
-// Various UI locations from which the companion page can be launched. Keep in
-// sync with Companion.OpenTrigger in enums.xml. These values are persisted to
-// logs. Entries should not be renumbered and numeric values should never be
-// reused.
-enum class OpenTrigger {
-  // Launch location is unknown.
-  kUnknown = 0,
-
-  // The companion page was opened via a context menu image search.
-  kContextMenuImageSearch = 1,
-
-  // The companion page was opened via a context menu text search.
-  kContextMenuTextSearch = 2,
-
-  // Other types of launches. Includes the toolbar button entry point.
-  kOther = 3,
-};
-
 // Tracks events happening on a single UI surface.
 struct UiSurfaceMetrics {
   UiSurfaceMetrics() = default;
@@ -111,7 +94,7 @@
   CompanionMetricsLogger& operator=(const CompanionMetricsLogger&) = delete;
   ~CompanionMetricsLogger();
 
-  void RecordOpenTrigger(OpenTrigger open_trigger);
+  void RecordOpenTrigger(absl::optional<SidePanelOpenTrigger> open_trigger);
 
   // For the following methods, please refer CompanionPageHandler in
   // companion.mojom for detailed documentation.
@@ -148,9 +131,9 @@
   // Last event on the promo surfaces.
   absl::optional<PhFeedback> last_ph_feedback_;
 
-  // Indicates how the companion page was opened. Non-empty for the first
+  // Indicates how the companion side panel was opened. Non-empty for the first
   // navigation.
-  absl::optional<OpenTrigger> open_trigger_;
+  absl::optional<SidePanelOpenTrigger> open_trigger_;
 };
 
 }  // namespace companion
diff --git a/chrome/browser/companion/core/companion_metrics_logger_unittest.cc b/chrome/browser/companion/core/companion_metrics_logger_unittest.cc
index e5df6309..c05f33db3 100644
--- a/chrome/browser/companion/core/companion_metrics_logger_unittest.cc
+++ b/chrome/browser/companion/core/companion_metrics_logger_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/companion/core/mojom/companion.mojom.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
@@ -72,13 +73,14 @@
 
 TEST_F(CompanionMetricsLoggerTest, RecordOpenTrigger) {
   base::HistogramTester histogram_tester;
-  logger_->RecordOpenTrigger(OpenTrigger::kContextMenuTextSearch);
+  logger_->RecordOpenTrigger(SidePanelOpenTrigger::kContextMenuSearchOption);
 
   // Destroy the logger. Verify that UKM event is recorded.
   logger_.reset();
 
-  ExpectUkmEntry(ukm::builders::Companion_PageView::kOpenTriggerName,
-                 static_cast<int>(OpenTrigger::kContextMenuTextSearch));
+  ExpectUkmEntry(
+      ukm::builders::Companion_PageView::kOpenTriggerName,
+      static_cast<int>(SidePanelOpenTrigger::kContextMenuSearchOption));
 }
 
 TEST_F(CompanionMetricsLoggerTest, RecordUiSurfaceShown) {
diff --git a/chrome/browser/companion/text_finder/text_finder_manager.h b/chrome/browser/companion/text_finder/text_finder_manager.h
index 547d9b8..c1c8b73 100644
--- a/chrome/browser/companion/text_finder/text_finder_manager.h
+++ b/chrome/browser/companion/text_finder/text_finder_manager.h
@@ -9,6 +9,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/companion/text_finder/text_finder.h"
diff --git a/chrome/browser/companion/text_finder/text_highlighter_manager.h b/chrome/browser/companion/text_finder/text_highlighter_manager.h
index 692a8a5..4bb40d5 100644
--- a/chrome/browser/companion/text_finder/text_highlighter_manager.h
+++ b/chrome/browser/companion/text_finder/text_highlighter_manager.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <unordered_map>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
 #include "content/public/browser/page_user_data.h"
diff --git a/chrome/browser/component_updater/file_type_policies_component_installer.h b/chrome/browser/component_updater/file_type_policies_component_installer.h
index 6723111..c2f3c61 100644
--- a/chrome/browser/component_updater/file_type_policies_component_installer.h
+++ b/chrome/browser/component_updater/file_type_policies_component_installer.h
@@ -6,12 +6,14 @@
 #define CHROME_BROWSER_COMPONENT_UPDATER_FILE_TYPE_POLICIES_COMPONENT_INSTALLER_H_
 
 #include <stdint.h>
+
 #include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
 #include "base/values.h"
 #include "components/component_updater/component_installer.h"
 
diff --git a/chrome/browser/dips/dips_database.h b/chrome/browser/dips/dips_database.h
index 5a2586d..60b01c4 100644
--- a/chrome/browser/dips/dips_database.h
+++ b/chrome/browser/dips/dips_database.h
@@ -18,9 +18,6 @@
 #include "sql/meta_table.h"
 #include "sql/statement.h"
 
-// TODO(crbug.com/1342228): This is currently in-memory only. Add support for a
-// persistent SQLite database to be used for non-OTR profiles.
-//
 // Encapsulates an SQL database that holds DIPS info.
 class DIPSDatabase {
  public:
diff --git a/chrome/browser/download/download_item_model_unittest.cc b/chrome/browser/download/download_item_model_unittest.cc
index 86be074..de91b8d 100644
--- a/chrome/browser/download/download_item_model_unittest.cc
+++ b/chrome/browser/download/download_item_model_unittest.cc
@@ -593,7 +593,7 @@
       {download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK,
        "Blocked by your organization"},
       {download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING,
-       "Scan before opening"},
+       "Scan for malware \xE2\x80\xA2 Suspicious"},
   };
   for (const auto& test_case : kDangerTypeTestCases) {
     SetupDownloadItemDefaults();
@@ -731,7 +731,7 @@
        {DownloadCommands::Command::DISCARD, DownloadCommands::Command::KEEP}},
       {download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING,
        false,
-       DownloadCommands::Command::DEEP_SCAN,
+       absl::nullopt,
        {DownloadCommands::Command::DEEP_SCAN,
         DownloadCommands::Command::BYPASS_DEEP_SCANNING}},
       {download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING,
@@ -795,7 +795,7 @@
        {DownloadCommands::Command::DISCARD, DownloadCommands::Command::KEEP}},
       {download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING,
        false,
-       DownloadCommands::Command::DEEP_SCAN,
+       absl::nullopt,
        {DownloadCommands::Command::DEEP_SCAN,
         DownloadCommands::Command::BYPASS_DEEP_SCANNING}},
       {download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING,
diff --git a/chrome/browser/download/download_ui_model.cc b/chrome/browser/download/download_ui_model.cc
index 1a5b3b41..b194875 100644
--- a/chrome/browser/download/download_ui_model.cc
+++ b/chrome/browser/download/download_ui_model.cc
@@ -1140,7 +1140,6 @@
           .AddIconAndColor(vector_icons::kNotSecureWarningIcon,
                            ui::kColorAlertMediumSeverityIcon)
           .AddSecondaryTextColor(ui::kColorAlertMediumSeverityText)
-          .AddPrimaryButton(DownloadCommands::Command::DEEP_SCAN)
           .AddSubpageButton(l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_SCAN),
                             DownloadCommands::Command::DEEP_SCAN,
                             /*is_prominent=*/true)
@@ -1434,9 +1433,12 @@
       return l10n_util::GetStringUTF16(
           IDS_DOWNLOAD_BUBBLE_INTERRUPTED_STATUS_BLOCKED_ORGANIZATION);
     case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
-      // "Scan before opening"
-      return l10n_util::GetStringUTF16(
-          IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT);
+      // "Scan for malware • Suspicious"
+      return l10n_util::GetStringFUTF16(
+          IDS_DOWNLOAD_BUBBLE_DOWNLOAD_STATUS_MESSAGE_WITH_SEPARATOR,
+          l10n_util::GetStringUTF16(
+              IDS_DOWNLOAD_BUBBLE_STATUS_DEEP_SCANNING_PROMPT),
+          l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_STATUS_SUSPICIOUS));
     case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
 #if BUILDFLAG(IS_ANDROID)
       // "Scanning..."
diff --git a/chrome/browser/download/download_ui_model.h b/chrome/browser/download/download_ui_model.h
index 47bc4d7..9d3362a 100644
--- a/chrome/browser/download/download_ui_model.h
+++ b/chrome/browser/download/download_ui_model.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
index 2ee6a55..8ab6754a 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -238,14 +238,11 @@
 }
 
 // static
-bool ContentAnalysisDelegate::IsEnabled(
-    Profile* profile,
-    GURL url,
-    Data* data,
-    enterprise_connectors::AnalysisConnector connector) {
-  auto* service =
-      enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
-          profile);
+bool ContentAnalysisDelegate::IsEnabled(Profile* profile,
+                                        GURL url,
+                                        Data* data,
+                                        AnalysisConnector connector) {
+  auto* service = ConnectorsServiceFactory::GetForBrowserContext(profile);
   // If the corresponding Connector policy isn't set, don't perform scans.
   if (!service || !service->IsConnectorEnabled(connector))
     return false;
@@ -271,8 +268,8 @@
     CompletionCallback callback,
     safe_browsing::DeepScanAccessPoint access_point) {
   Factory* testing_factory = GetFactoryStorage();
-  bool wait_for_verdict = data.settings.block_until_verdict ==
-                          enterprise_connectors::BlockUntilVerdict::kBlock;
+  bool wait_for_verdict =
+      data.settings.block_until_verdict == BlockUntilVerdict::kBlock;
   // Using new instead of std::make_unique<> to access non public constructor.
   auto delegate = testing_factory->is_null()
                       ? base::WrapUnique(new ContentAnalysisDelegate(
@@ -370,7 +367,7 @@
 
 void ContentAnalysisDelegate::StringRequestCallback(
     BinaryUploadService::Result result,
-    enterprise_connectors::ContentAnalysisResponse response) {
+    ContentAnalysisResponse response) {
   // Remember to send an ack for this response.
   if (result == safe_browsing::BinaryUploadService::Result::SUCCESS)
     final_actions_[response.request_token()] = GetAckFinalAction(response);
@@ -414,7 +411,7 @@
 
 void ContentAnalysisDelegate::ImageRequestCallback(
     BinaryUploadService::Result result,
-    enterprise_connectors::ContentAnalysisResponse response) {
+    ContentAnalysisResponse response) {
   // Remember to send an ack for this response.
   if (result == safe_browsing::BinaryUploadService::Result::SUCCESS) {
     final_actions_[response.request_token()] = GetAckFinalAction(response);
@@ -497,7 +494,7 @@
 
 void ContentAnalysisDelegate::PageRequestCallback(
     BinaryUploadService::Result result,
-    enterprise_connectors::ContentAnalysisResponse response) {
+    ContentAnalysisResponse response) {
   // Remember to send an ack for this response.
   if (result == safe_browsing::BinaryUploadService::Result::SUCCESS)
     final_actions_[response.request_token()] = GetAckFinalAction(response);
@@ -590,7 +587,7 @@
         base::BindOnce(&ContentAnalysisDelegate::StringRequestCallback,
                        weak_ptr_factory_.GetWeakPtr()));
 
-    PrepareRequest(enterprise_connectors::BULK_DATA_ENTRY, request.get());
+    PrepareRequest(BULK_DATA_ENTRY, request.get());
     UploadTextForDeepScanning(std::move(request));
   }
 }
@@ -617,7 +614,7 @@
         base::BindOnce(&ContentAnalysisDelegate::ImageRequestCallback,
                        weak_ptr_factory_.GetWeakPtr()));
 
-    PrepareRequest(enterprise_connectors::BULK_DATA_ENTRY, request.get());
+    PrepareRequest(BULK_DATA_ENTRY, request.get());
     UploadImageForDeepScanning(std::move(request));
   }
 }
@@ -634,9 +631,10 @@
         base::BindOnce(&ContentAnalysisDelegate::PageRequestCallback,
                        weak_ptr_factory_.GetWeakPtr()));
 
-    PrepareRequest(enterprise_connectors::PRINT, request.get());
+    PrepareRequest(PRINT, request.get());
     request->set_filename(title_);
     request->set_printer_name(data_.printer_name);
+    request->set_printer_type(data_.printer_type);
     if (!page_content_type_.empty()) {
       request->set_content_type(page_content_type_);
     }
@@ -648,7 +646,7 @@
 // are handled by
 // chrome/browser/enterprise/connectors/analysis/files_request_handler.h
 void ContentAnalysisDelegate::PrepareRequest(
-    enterprise_connectors::AnalysisConnector connector,
+    AnalysisConnector connector,
     BinaryUploadService::Request* request) {
   if (data_.settings.cloud_or_local_settings.is_cloud_analysis()) {
     request->set_device_token(
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
index 248f147..e4ba18f 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
@@ -116,8 +116,14 @@
     // Printer name of the page being sent to, empty for non-print actions.
     std::string printer_name;
 
+    // TODO(b/280457160): Set printer type once scan after printer preview
+    // is done.
+    // Printer type of the page being sent to, the default value is UNKNOWN.
+    ContentMetaData::PrintMetadata::PrinterType printer_type =
+        ContentMetaData::PrintMetadata::UNKNOWN;
+
     // The settings to use for the analysis of the data in this struct.
-    enterprise_connectors::AnalysisSettings settings;
+    AnalysisSettings settings;
   };
 
   // Result of deep scanning.  Each Result contains the verdicts of deep scans
@@ -200,7 +206,7 @@
   static bool IsEnabled(Profile* profile,
                         GURL url,
                         Data* data,
-                        enterprise_connectors::AnalysisConnector connector);
+                        AnalysisConnector connector);
 
   // Entry point for starting a deep scan, with the callback being called once
   // all results are available.  When the UI is enabled, a tab-modal dialog
@@ -242,15 +248,12 @@
   // testing derived classes.
   // TODO(crbug.com/1324892): Adapt once TextRequestHandler and
   // PageRequestHandler are created and move reporting to the RequestHandlers.
-  void StringRequestCallback(
-      safe_browsing::BinaryUploadService::Result result,
-      enterprise_connectors::ContentAnalysisResponse response);
-  void ImageRequestCallback(
-      safe_browsing::BinaryUploadService::Result result,
-      enterprise_connectors::ContentAnalysisResponse response);
-  void PageRequestCallback(
-      safe_browsing::BinaryUploadService::Result result,
-      enterprise_connectors::ContentAnalysisResponse response);
+  void StringRequestCallback(safe_browsing::BinaryUploadService::Result result,
+                             ContentAnalysisResponse response);
+  void ImageRequestCallback(safe_browsing::BinaryUploadService::Result result,
+                            ContentAnalysisResponse response);
+  void PageRequestCallback(safe_browsing::BinaryUploadService::Result result,
+                           ContentAnalysisResponse response);
 
   // Callback called after all files are scanned by the FilesRequestHandler.
   void FilesRequestCallback(std::vector<RequestHandlerResult> results);
@@ -298,7 +301,7 @@
   // service.
   // TODO(crbug.com/1324892): Remove once TextRequestHandler and
   // PageRequestHandler are created.
-  void PrepareRequest(enterprise_connectors::AnalysisConnector connector,
+  void PrepareRequest(AnalysisConnector connector,
                       safe_browsing::BinaryUploadService::Request* request);
 
   // Fills the arrays in `result_` with the given boolean status.
@@ -362,18 +365,18 @@
 
   // Set to true if the full text got a DLP warning verdict.
   bool text_warning_ = false;
-  enterprise_connectors::ContentAnalysisResponse text_response_;
+  ContentAnalysisResponse text_response_;
 
   // Set to true if the full image got a DLP warning verdict.
   bool image_warning_ = false;
-  enterprise_connectors::ContentAnalysisResponse image_response_;
+  ContentAnalysisResponse image_response_;
 
   // Indices of warned files.
   std::vector<size_t> warned_file_indices_;
 
   // Set to true if the printed page got a DLP warning verdict.
   bool page_warning_ = false;
-  enterprise_connectors::ContentAnalysisResponse page_response_;
+  ContentAnalysisResponse page_response_;
 
   // Stores the scanned page's size since it moves from `data_` to be uploaded.
   // TODO(crbug.com/1324892): Move to PageRequestHandler.
diff --git a/chrome/browser/enterprise/connectors/device_trust/BUILD.gn b/chrome/browser/enterprise/connectors/device_trust/BUILD.gn
index 5bfd4ad..cc578281d 100644
--- a/chrome/browser/enterprise/connectors/device_trust/BUILD.gn
+++ b/chrome/browser/enterprise/connectors/device_trust/BUILD.gn
@@ -33,10 +33,12 @@
   public_deps = [
     "//base",
     "//chrome/browser",
+    "//chrome/browser/enterprise/connectors/device_trust/common",
     "//testing/gmock",
   ]
 
   deps = [
+    ":features",
     ":prefs",
     "//components/pref_registry",
     "//components/prefs:test_support",
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.cc
similarity index 73%
rename from chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.cc
rename to chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.cc
index 5530b65..dbc8ed2d 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.cc
@@ -1,8 +1,8 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
+// 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.
 
-#include "chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h"
 
 namespace enterprise_connectors::switches {
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h
similarity index 70%
rename from chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h
rename to chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h
index 341cf84..51ef9b6 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h
@@ -1,9 +1,9 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
+// 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.
 
-#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_DESKTOP_ATTESTATION_SWITCHES_H_
-#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_DESKTOP_ATTESTATION_SWITCHES_H_
+#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_ATTESTATION_SWITCHES_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_ATTESTATION_SWITCHES_H_
 
 namespace enterprise_connectors::switches {
 
@@ -12,4 +12,4 @@
 
 }  // namespace enterprise_connectors::switches
 
-#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_DESKTOP_ATTESTATION_SWITCHES_H_
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_ATTESTATION_SWITCHES_H_
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.cc
new file mode 100644
index 0000000..de86fba
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.cc
@@ -0,0 +1,251 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.h"
+
+#include <utility>
+
+#include "base/barrier_closure.h"
+#include "base/check.h"
+#include "base/command_line.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/values.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_utils.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.pb.h"
+#include "chrome/browser/enterprise/connectors/device_trust/common/common_types.h"
+#include "crypto/random.h"
+
+namespace enterprise_connectors {
+
+namespace {
+
+// Size of nonce for challenge response.
+const size_t kChallengeResponseNonceBytesSize = 32;
+
+// Verifies that the `signed_challenge_data` comes from Verified Access.
+bool ChallengeComesFromVerifiedAccess(
+    const SignedData& signed_challenge_data,
+    const std::string& va_public_key_modulus_hex) {
+  // Verify challenge signature.
+  return CryptoUtility::VerifySignatureUsingHexKey(
+      va_public_key_modulus_hex, signed_challenge_data.data(),
+      signed_challenge_data.signature());
+}
+
+VAType GetVAType() {
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kUseVaDevKeys)) {
+    return VAType::TEST_VA;
+  }
+  return VAType::DEFAULT_VA;
+}
+
+// The KeyInfo message encrypted using a public encryption key, with
+// the following parameters:
+//   Key encryption: RSA-OAEP with no custom parameters.
+//   Data encryption: 256-bit key, AES-CBC with PKCS5 padding.
+//   MAC: HMAC-SHA-512 using the AES key.
+absl::optional<std::string> CreateChallengeResponseString(
+    const std::string& serialized_key_info,
+    const SignedData& signed_challenge_data,
+    const std::string& wrapping_key_modulus_hex,
+    const std::string& wrapping_key_id) {
+  ChallengeResponse response_pb;
+  *response_pb.mutable_challenge() = signed_challenge_data;
+
+  crypto::RandBytes(base::WriteInto(response_pb.mutable_nonce(),
+                                    kChallengeResponseNonceBytesSize + 1),
+                    kChallengeResponseNonceBytesSize);
+
+  std::string key;
+  if (!CryptoUtility::EncryptWithSeed(
+          serialized_key_info, response_pb.mutable_encrypted_key_info(), key)) {
+    return absl::nullopt;
+  }
+
+  bssl::UniquePtr<RSA> rsa(CryptoUtility::GetRSA(wrapping_key_modulus_hex));
+  if (!rsa) {
+    return absl::nullopt;
+  }
+
+  if (!CryptoUtility::WrapKeyOAEP(key, rsa.get(), wrapping_key_id,
+                                  response_pb.mutable_encrypted_key_info())) {
+    return absl::nullopt;
+  }
+
+  // Convert the challenge response proto to a string before returning it.
+  std::string serialized_response;
+  if (!response_pb.SerializeToString(&serialized_response)) {
+    return absl::nullopt;
+  }
+  return serialized_response;
+}
+
+}  // namespace
+
+BrowserAttestationService::BrowserAttestationService(
+    std::vector<std::unique_ptr<Attester>> attesters)
+    : attesters_(std::move(attesters)),
+      background_task_runner_(base::ThreadPool::CreateTaskRunner(
+          {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
+  CHECK(attesters_.size() > 0);
+}
+
+BrowserAttestationService::~BrowserAttestationService() = default;
+
+// Goes through the following steps in order:
+// - Validate challenge comes from VA,
+// - Generated challenge response,
+// - Sign response,
+// - Encode encrypted data,
+// - Reply to callback.
+void BrowserAttestationService::BuildChallengeResponseForVAChallenge(
+    const std::string& challenge,
+    base::Value::Dict signals,
+    const std::set<DTCPolicyLevel>& levels,
+    AttestationCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  SignedData signed_data;
+  if (challenge.empty() || !signed_data.ParseFromString(challenge)) {
+    // Challenge is not properly formatted, so mark the device as untrusted (no
+    // challenge response).
+    std::move(callback).Run(
+        {std::string(), DTAttestationResult::kBadChallengeFormat});
+    return;
+  }
+
+  background_task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&ChallengeComesFromVerifiedAccess, signed_data,
+                     google_keys_.va_signing_key(GetVAType()).modulus_in_hex()),
+      base::BindOnce(&BrowserAttestationService::OnChallengeValidated,
+                     weak_factory_.GetWeakPtr(), signed_data,
+                     std::move(signals), levels, std::move(callback)));
+}
+
+void BrowserAttestationService::OnChallengeValidated(
+    const SignedData& signed_data,
+    base::Value::Dict signals,
+    const std::set<DTCPolicyLevel>& levels,
+    AttestationCallback callback,
+    bool is_va_challenge) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!is_va_challenge) {
+    // Challenge does not come from VA, so mark the device as untrusted (no
+    // challenge response).
+    std::move(callback).Run(
+        {std::string(), DTAttestationResult::kBadChallengeSource});
+    return;
+  }
+
+  // Fill `key_info` out for Chrome Browser.
+  KeyInfo key_info;
+  key_info.set_key_type(CBCM);
+  // VA should accept signals JSON string.
+  std::string signals_json;
+  if (!base::JSONWriter::Write(signals, &signals_json)) {
+    std::move(callback).Run(
+        {std::string(), DTAttestationResult::kFailedToSerializeSignals});
+    return;
+  }
+  key_info.set_device_trust_signals_json(signals_json);
+
+  // Populate profile and/or device level information.
+  auto barrier_closure = base::BarrierClosure(
+      /*num_closures=*/attesters_.size(),
+      base::BindOnce(&BrowserAttestationService::OnKeyInfoDecorated,
+                     weak_factory_.GetWeakPtr(), signed_data, levels,
+                     std::move(callback), std::ref(key_info)));
+
+  for (const auto& attester : attesters_) {
+    attester->DecorateKeyInfo(levels, std::ref(key_info), barrier_closure);
+  }
+}
+
+void BrowserAttestationService::OnKeyInfoDecorated(
+    const SignedData& signed_data,
+    const std::set<DTCPolicyLevel>& levels,
+    AttestationCallback callback,
+    KeyInfo& key_info) {
+  std::string serialized_key_info;
+  if (!key_info.SerializeToString(&serialized_key_info)) {
+    std::move(callback).Run(
+        {std::string(), DTAttestationResult::kFailedToSerializeKeyInfo});
+    return;
+  }
+
+  auto va_encryption_key = google_keys_.va_encryption_key(GetVAType());
+  background_task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&CreateChallengeResponseString, serialized_key_info,
+                     signed_data, va_encryption_key.modulus_in_hex(),
+                     va_encryption_key.key_id()),
+      base::BindOnce(&BrowserAttestationService::OnResponseCreated,
+                     weak_factory_.GetWeakPtr(), levels, std::move(callback)));
+}
+
+void BrowserAttestationService::OnResponseCreated(
+    const std::set<DTCPolicyLevel>& levels,
+    AttestationCallback callback,
+    absl::optional<std::string> encrypted_response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!encrypted_response) {
+    // Failed to create a response, so mark the device as untrusted (no
+    // challenge response).
+    std::move(callback).Run(
+        {std::string(), DTAttestationResult::kFailedToGenerateResponse});
+    return;
+  }
+
+  // Add profile and/or device signature to the signed data.
+  SignedData signed_data;
+  auto barrier_closure = base::BarrierClosure(
+      /*num_closures=*/attesters_.size(),
+      base::BindOnce(&BrowserAttestationService::OnResponseSigned,
+                     weak_factory_.GetWeakPtr(), std::move(callback),
+                     encrypted_response.value(), std::ref(signed_data)));
+
+  for (const auto& attester : attesters_) {
+    attester->SignResponse(levels, encrypted_response.value(),
+                           std::ref(signed_data), barrier_closure);
+  }
+}
+
+void BrowserAttestationService::OnResponseSigned(
+    AttestationCallback callback,
+    const std::string& encrypted_response,
+    SignedData& signed_data) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Encode the challenge-response values into a JSON string and return them.
+  signed_data.set_data(encrypted_response);
+
+  std::string serialized_attestation_response;
+  if (!signed_data.SerializeToString(&serialized_attestation_response)) {
+    std::move(callback).Run(
+        {std::string(), DTAttestationResult::kFailedToSerializeResponse});
+    return;
+  }
+
+  std::string json_response;
+  if (!serialized_attestation_response.empty()) {
+    json_response =
+        ProtobufChallengeToJsonChallenge(serialized_attestation_response);
+  }
+
+  std::move(callback).Run(
+      {json_response, json_response.empty()
+                          ? DTAttestationResult::kEmptySerializedResponse
+                      : signed_data.has_signature()
+                          ? DTAttestationResult::kSuccess
+                          : DTAttestationResult::kSuccessNoSignature});
+}
+
+}  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.h b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.h
new file mode 100644
index 0000000..36c9fb1
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.h
@@ -0,0 +1,79 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_BROWSER_ATTESTATION_SERVICE_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_BROWSER_ATTESTATION_SERVICE_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/task_runner.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/attester.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_service.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace enterprise_connectors {
+
+// This class provides the methods needed in the handshake between Chrome, an
+// IdP and Verified Access.
+class BrowserAttestationService : public AttestationService {
+ public:
+  explicit BrowserAttestationService(
+      std::vector<std::unique_ptr<Attester>> attesters);
+  ~BrowserAttestationService() override;
+
+  // AttestationService:
+  void BuildChallengeResponseForVAChallenge(
+      const std::string& challenge,
+      base::Value::Dict signals,
+      const std::set<DTCPolicyLevel>& levels,
+      AttestationCallback callback) override;
+
+ private:
+  void OnChallengeParsed(AttestationCallback callback,
+                         base::Value::Dict signals,
+                         const std::string& serialized_signed_challenge);
+
+  void OnChallengeValidated(const SignedData& signed_data,
+                            base::Value::Dict signals,
+                            const std::set<DTCPolicyLevel>& levels,
+                            AttestationCallback callback,
+                            bool is_va_challenge);
+
+  void OnKeyInfoDecorated(const SignedData& signed_data,
+                          const std::set<DTCPolicyLevel>& levels,
+                          AttestationCallback callback,
+                          KeyInfo& key_info);
+
+  void OnResponseCreated(const std::set<DTCPolicyLevel>& levels,
+                         AttestationCallback callback,
+                         absl::optional<std::string> encrypted_response);
+
+  void OnResponseSigned(AttestationCallback callback,
+                        const std::string& encrypted_response,
+                        SignedData& signed_data);
+
+  GoogleKeys google_keys_;
+
+  // Array of attesters each of which carry out specific attestation actions
+  // respective to their policy level.
+  std::vector<std::unique_ptr<Attester>> attesters_;
+
+  // Runner for tasks needed to be run in the background.
+  scoped_refptr<base::TaskRunner> background_task_runner_;
+
+  // Checker used to validate that non-background tasks should be
+  // running on the original sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<BrowserAttestationService> weak_factory_{this};
+};
+
+}  // namespace enterprise_connectors
+
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_BROWSER_ATTESTATION_SERVICE_H_
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service_unittest.cc
new file mode 100644
index 0000000..05d1d63
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service_unittest.cc
@@ -0,0 +1,333 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service.h"
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "base/values.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_utils.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.pb.h"
+#include "components/device_signals/core/common/signals_constants.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Invoke;
+using testing::StrictMock;
+
+namespace enterprise_connectors {
+
+namespace {
+
+class MockAttester : public Attester {
+ public:
+  MockAttester() {}
+  ~MockAttester() override = default;
+
+  // Attester:
+  MOCK_METHOD(void,
+              DecorateKeyInfo,
+              (const std::set<DTCPolicyLevel>&, KeyInfo&, base::OnceClosure),
+              (override));
+  MOCK_METHOD(void,
+              SignResponse,
+              (const std::set<DTCPolicyLevel>&,
+               const std::string&,
+               SignedData&,
+               base::OnceClosure),
+              (override));
+};
+
+// A sample VerifiedAccess v2 challenge.
+constexpr char kEncodedChallenge[] =
+    "CkEKFkVudGVycHJpc2VLZXlDaGFsbGVuZ2USIELlPXqh8+"
+    "rZJ2VIqwPXtPFrr653QdRrIzHFwqP+"
+    "b3L8GJTcufirLxKAAkindNwTfwYUcbCFDjiW3kXdmDPE0wC0J6b5ZI6X6vOVcSMXTpK7nxsAGK"
+    "zFV+i80LCnfwUZn7Ne1bHzloAqBdpLOu53vQ63hKRk6MRPhc9jYVDsvqXfQ7s+"
+    "FUA5r3lxdoluxwAUMFqcP4VgnMvKzKTPYbnnB+xj5h5BZqjQToXJYoP4VC3/"
+    "ID+YHNsCWy5o7+G5jnq0ak3zeqWfo1+lCibMPsCM+"
+    "2g7nCZIwvwWlfoKwv3aKvOVMBcJxPAIxH1w+hH+"
+    "NWxqRi6qgZm84q0ylm0ybs6TFjdgLvSViAIp0Z9p/An/"
+    "u3W4CMboCswxIxNYRCGrIIVPElE3Yb4QS65mKrg=";
+
+constexpr char kEncodedChallengeNotFromVA[] =
+    "CkEKFkVudGVycHJpc2VLZXlDaGFsbGVuZ2USIELlPXqh8+"
+    "rZJ2VIqwPXtPFrr653QdRrIzHFwqP+"
+    "b3L8GJTcufirLxKAAkindNwTfwYUcbCFDjiW3kXdmDPE0wC0J6b5ZI6X6vOVcSMXTpK7nxsAGK"
+    "zFV+i80LCnfwUZn7Ne1bHzloAqBdpLOu53vQ63hKRk6MRPhc9jYVDsvqXfQ7s+"
+    "FUA5r3lxdoluxwAUMFqcP4VgnMvKzKTPYbnnB+xj5h5BZqjQToXJYoP4VC3/"
+    "ID+YHNsCWy5o7+G5jnq0ak3zeqWfo1+lCibMPsCM+"
+    "2g7nCZIwvwWlfoKwv3aKvOVMBcJxPAIxH1w+hH+"
+    "NWxqRi6qgZm84q0ylm0ybs6TFjdgLvSViAIp0Z9p/An/"
+    "u3W4CMboCswxIxNYRCGrIIVPElE3Yb4QS123123=";
+
+constexpr char kEncodedChallengeDev[] =
+    "CkEKFkVudGVycHJpc2VLZXlDaGFsbGVuZ2USIK8RHA0BfjJvELuaGMIdh731PGNb/"
+    "xr1iGTm7Ycs78S9GM7Yo/"
+    "idMBKAAmOlxSwClQS56he7BwRdARhbqG7m6XO9YqhzssvMYKJ2uoOxdCH+FNzC8j/"
+    "Kbcaq0aWoKtJUmjYJ2vJoeG0ZwMKFamHO85RRC7LvX5M3czQlJkv/"
+    "wZd3KgSbMi1wDa86LWxMIJV7uBbRlkaXDGsaHGIbpqumrzX3J1f5cPRrvHQG6XHlbjBd+"
+    "eXoE4tQwcHuTKc8ywPv0bmQ7kHtRhk1VRRpDcijSfp/"
+    "2Q99GqWGtFS5MjCSQxwHQ2OAxr74aRYCY4mvnWLnLd02IvO9PhRa1fncT+"
+    "AhOmbMq35XWmRDwPAcAf+bE23yYeur3E5V8nKulZRkVTcTbE7g3ymsrlbsCSU=";
+
+constexpr char kDisplayName[] = "display_name";
+
+constexpr char kFakeSignature[] = "fake_signature";
+
+std::string GetSerializedSignedChallenge(bool use_dev = false) {
+  std::string serialized_signed_challenge;
+  if (!base::Base64Decode(use_dev ? kEncodedChallengeDev : kEncodedChallenge,
+                          &serialized_signed_challenge)) {
+    return std::string();
+  }
+
+  return serialized_signed_challenge;
+}
+
+absl::optional<SignedData> ParseDataFromResponse(const std::string& response) {
+  absl::optional<base::Value> data = base::JSONReader::Read(
+      response, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+
+  // If json is malformed or it doesn't include the needed field return
+  // an empty string.
+  if (!data || !data->GetDict().FindString("challengeResponse")) {
+    return absl::nullopt;
+  }
+
+  std::string serialized_signed_challenge;
+  if (!base::Base64Decode(*data->GetDict().FindString("challengeResponse"),
+                          &serialized_signed_challenge)) {
+    return absl::nullopt;
+  }
+
+  SignedData signed_data;
+  if (!signed_data.ParseFromString(serialized_signed_challenge)) {
+    return absl::nullopt;
+  }
+  return signed_data;
+}
+
+}  // namespace
+
+class BrowserAttestationServiceTest : public testing::Test {
+ protected:
+  BrowserAttestationServiceTest() = default;
+
+  void SetUp() override {
+    testing::Test::SetUp();
+
+    auto mock_device_attester = std::make_unique<MockAttester>();
+    mock_device_attester_ = mock_device_attester.get();
+
+    auto mock_profile_attester = std::make_unique<MockAttester>();
+    mock_profile_attester_ = mock_profile_attester.get();
+
+    std::vector<std::unique_ptr<Attester>> attesters;
+    attesters.push_back(std::move(mock_profile_attester));
+    attesters.push_back(std::move(mock_device_attester));
+
+    attestation_service_ =
+        std::make_unique<BrowserAttestationService>(std::move(attesters));
+  }
+
+  base::Value::Dict CreateSignals() {
+    base::Value::Dict signals;
+    signals.Set(device_signals::names::kDisplayName, kDisplayName);
+    return signals;
+  }
+
+  void MockDecorateKeyInfo() {
+    EXPECT_CALL(*mock_device_attester_, DecorateKeyInfo(_, _, _))
+        .WillOnce(Invoke([](const std::set<DTCPolicyLevel>& levels,
+                            KeyInfo& key_info, base::OnceClosure done_closure) {
+          std::move(done_closure).Run();
+        }));
+
+    EXPECT_CALL(*mock_profile_attester_, DecorateKeyInfo(_, _, _))
+        .WillOnce(Invoke([](const std::set<DTCPolicyLevel>& levels,
+                            KeyInfo& key_info, base::OnceClosure done_closure) {
+          std::move(done_closure).Run();
+        }));
+  }
+
+  void MockSignResponse(bool add_browser_signature = true) {
+    EXPECT_CALL(*mock_device_attester_, SignResponse(_, _, _, _))
+        .WillOnce(Invoke(
+            [add_browser_signature](const std::set<DTCPolicyLevel>& levels,
+                                    const std::string& challenge_response,
+                                    SignedData& signed_data,
+                                    base::OnceClosure done_closure) {
+              ASSERT_FALSE(challenge_response.empty());
+              if ((levels.find(DTCPolicyLevel::kBrowser) != levels.end()) &&
+                  add_browser_signature) {
+                signed_data.set_signature(kFakeSignature);
+              }
+              std::move(done_closure).Run();
+            }));
+
+    EXPECT_CALL(*mock_profile_attester_, SignResponse(_, _, _, _))
+        .WillOnce(
+            Invoke([](const std::set<DTCPolicyLevel>& levels,
+                      const std::string& challenge_response,
+                      SignedData& signed_data, base::OnceClosure done_closure) {
+              ASSERT_FALSE(challenge_response.empty());
+              std::move(done_closure).Run();
+            }));
+  }
+
+  void VerifyAttestationResponse(
+      const AttestationResponse& attestation_response,
+      bool has_signature = true) {
+    ASSERT_FALSE(attestation_response.challenge_response.empty());
+    auto signed_data =
+        ParseDataFromResponse(attestation_response.challenge_response);
+    ASSERT_TRUE(signed_data);
+    EXPECT_FALSE(signed_data->data().empty());
+    EXPECT_EQ(signed_data->signature().empty(), !has_signature);
+
+    EXPECT_EQ(attestation_response.result_code,
+              has_signature ? DTAttestationResult::kSuccess
+                            : DTAttestationResult::kSuccessNoSignature);
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<BrowserAttestationService> attestation_service_;
+  raw_ptr<MockAttester> mock_device_attester_;
+  raw_ptr<MockAttester> mock_profile_attester_;
+};
+
+// Test building the challenge response when the policy is enabled at both the
+// user and browser-level using a Dev VA Challenge.
+TEST_F(BrowserAttestationServiceTest, BuildChallengeResponseDev_Success) {
+  auto levels = std::set<DTCPolicyLevel>(
+      {DTCPolicyLevel::kBrowser, DTCPolicyLevel::kUser});
+  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+      switches::kUseVaDevKeys, "");
+  MockDecorateKeyInfo();
+  MockSignResponse();
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      GetSerializedSignedChallenge(/* use_dev= */ true), CreateSignals(),
+      levels, future.GetCallback());
+
+  VerifyAttestationResponse(future.Get());
+}
+
+// Test building the challenge response when the policy is enabled at both the
+// user and browser-level using a Prod VA Challenge.
+TEST_F(BrowserAttestationServiceTest, BuildChallengeResponseProd_Success) {
+  auto levels = std::set<DTCPolicyLevel>(
+      {DTCPolicyLevel::kBrowser, DTCPolicyLevel::kUser});
+  MockDecorateKeyInfo();
+  MockSignResponse();
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      GetSerializedSignedChallenge(), CreateSignals(), levels,
+      future.GetCallback());
+
+  VerifyAttestationResponse(future.Get());
+}
+
+// Test building the challenge response when the challenge is missing.
+TEST_F(BrowserAttestationServiceTest, BuildChallengeResponse_EmptyChallenge) {
+  auto levels = std::set<DTCPolicyLevel>(
+      {DTCPolicyLevel::kBrowser, DTCPolicyLevel::kUser});
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      "", CreateSignals(), levels, future.GetCallback());
+
+  const auto& attestation_response = future.Get();
+  ASSERT_TRUE(attestation_response.challenge_response.empty());
+  EXPECT_EQ(attestation_response.result_code,
+            DTAttestationResult::kBadChallengeFormat);
+}
+
+// Test building the challenge response when the challenge is incorrect.
+TEST_F(BrowserAttestationServiceTest,
+       BuildChallengeResponse_BadChallengeSource) {
+  auto levels = std::set<DTCPolicyLevel>(
+      {DTCPolicyLevel::kBrowser, DTCPolicyLevel::kUser});
+  std::string challenge_not_from_va;
+  base::Base64Decode(kEncodedChallengeNotFromVA, &challenge_not_from_va);
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      challenge_not_from_va, CreateSignals(), levels, future.GetCallback());
+
+  const auto& attestation_response = future.Get();
+  ASSERT_TRUE(attestation_response.challenge_response.empty());
+  EXPECT_EQ(attestation_response.result_code,
+            DTAttestationResult::kBadChallengeSource);
+}
+
+// Test building the challenge response when the policy is enabled at the
+// browser-level only.
+TEST_F(BrowserAttestationServiceTest, BuildChallengeResponse_BrowserOnly) {
+  MockDecorateKeyInfo();
+  MockSignResponse();
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      GetSerializedSignedChallenge(), CreateSignals(),
+      std::set<DTCPolicyLevel>({DTCPolicyLevel::kBrowser}),
+      future.GetCallback());
+
+  VerifyAttestationResponse(future.Get());
+}
+
+// Test building the challenge response when the policy is enabled at the
+// browser-level only and no signature is set by the device attester.
+TEST_F(BrowserAttestationServiceTest,
+       BuildChallengeResponse_BrowserOnly_MissingSignature) {
+  MockDecorateKeyInfo();
+  MockSignResponse(/*add_browser_signature=*/false);
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      GetSerializedSignedChallenge(), CreateSignals(),
+      std::set<DTCPolicyLevel>({DTCPolicyLevel::kBrowser}),
+      future.GetCallback());
+
+  VerifyAttestationResponse(future.Get(), /*has_signature=*/false);
+}
+
+// Test building the challenge response when the policy is enabled at the
+// user-level only.
+TEST_F(BrowserAttestationServiceTest, BuildChallengeResponse_ProfileOnly) {
+  MockDecorateKeyInfo();
+  MockSignResponse();
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      GetSerializedSignedChallenge(), CreateSignals(),
+      std::set<DTCPolicyLevel>({DTCPolicyLevel::kUser}), future.GetCallback());
+
+  VerifyAttestationResponse(future.Get(), /*has_signature=*/false);
+}
+
+// Test building the challenge response when the policy is not enabled at all.
+TEST_F(BrowserAttestationServiceTest,
+       BuildChallengeResponse_EmptyPolicyLevels) {
+  MockDecorateKeyInfo();
+  MockSignResponse();
+
+  base::test::TestFuture<const AttestationResponse&> future;
+  attestation_service_->BuildChallengeResponseForVAChallenge(
+      GetSerializedSignedChallenge(), CreateSignals(),
+      std::set<DTCPolicyLevel>(), future.GetCallback());
+
+  VerifyAttestationResponse(future.Get(), /*has_signature=*/false);
+}
+
+}  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.cc
similarity index 97%
rename from chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.cc
rename to chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.cc
index 4307569..2aa6137 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.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/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.h"
 
 #include "base/containers/span.h"
 #include "base/logging.h"
@@ -92,8 +92,9 @@
   crypto::SignatureVerifier verifier;
   if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA256,
                            base::as_bytes(base::make_span(signature)),
-                           base::as_bytes(base::make_span(public_key_info))))
+                           base::as_bytes(base::make_span(public_key_info)))) {
     return false;
+  }
 
   verifier.VerifyUpdate(base::as_bytes(base::make_span(data)));
   return verifier.VerifyFinal();
@@ -115,8 +116,9 @@
   std::unique_ptr<crypto::SymmetricKey> symmetric_key(
       crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES,
                                               kAesKeySizeBits));
-  if (!symmetric_key)
+  if (!symmetric_key) {
     return false;
+  }
   key = symmetric_key->key();
 
   // Generate initialized vector of size 128 bits.
@@ -124,10 +126,12 @@
   crypto::RandBytes(base::WriteInto(&iv, kAesBlockSize + 1), kAesBlockSize);
 
   crypto::Encryptor encryptor;
-  if (!encryptor.Init(symmetric_key.get(), crypto::Encryptor::CBC, iv))
+  if (!encryptor.Init(symmetric_key.get(), crypto::Encryptor::CBC, iv)) {
     return false;
-  if (!encryptor.Encrypt(data, encrypted->mutable_encrypted_data()))
+  }
+  if (!encryptor.Encrypt(data, encrypted->mutable_encrypted_data())) {
     return false;
+  }
 
   encrypted->set_iv(iv);
   encrypted->set_mac(HmacSha512(key, iv + encrypted->encrypted_data()));
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.h b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.h
similarity index 95%
rename from chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.h
rename to chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.h
index 4c94e97..32b7ad7a 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.h
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.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 CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_CRYPTO_UTILITY_H_
-#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_CRYPTO_UTILITY_H_
+#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_CRYPTO_UTILITY_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_CRYPTO_UTILITY_H_
 
 #include "chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.pb.h"
 #include "third_party/boringssl/src/include/openssl/rsa.h"
@@ -46,4 +46,4 @@
 
 }  // namespace enterprise_connectors
 
-#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_CRYPTO_UTILITY_H_
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_CRYPTO_UTILITY_H_
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.cc
similarity index 99%
rename from chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.cc
rename to chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.cc
index fcbf3bb..4bbbe4e1 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.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/enterprise/connectors/device_trust/attestation/desktop/google_keys.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.h"
 
 namespace enterprise_connectors {
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.h b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.h
similarity index 93%
rename from chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.h
rename to chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.h
index fe6bb73..231d2915 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.h
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.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 CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_GOOGLE_KEYS_H_
-#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_GOOGLE_KEYS_H_
+#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_GOOGLE_KEYS_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_GOOGLE_KEYS_H_
 
 #include <array>
 #include <string>
@@ -37,4 +37,4 @@
 
 }  // namespace enterprise_connectors
 
-#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_DESKTOP_GOOGLE_KEYS_H_
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_BROWSER_GOOGLE_KEYS_H_
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.cc
index 0dc0038..6f03e3e 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.cc
@@ -14,10 +14,10 @@
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/values.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/crypto_utility.h"
 #include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_utils.h"
 #include "chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.pb.h"
-#include "chrome/browser/enterprise/connectors/device_trust/attestation/desktop/crypto_utility.h"
-#include "chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h"
 #include "chrome/browser/enterprise/connectors/device_trust/common/common_types.h"
 #include "chrome/browser/enterprise/connectors/device_trust/common/metrics_utils.h"
 #include "components/device_signals/core/common/signals_constants.h"
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.h b/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.h
index 60b024e3..6abf2a6 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.h
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service.h
@@ -13,8 +13,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/task/task_runner.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.h"
 #include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_service.h"
-#include "chrome/browser/enterprise/connectors/device_trust/attestation/desktop/google_keys.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace policy {
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service_unittest.cc
index 7b429ab..f1ecbf15f 100644
--- a/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service_unittest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service_unittest.cc
@@ -12,9 +12,9 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "base/values.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation/browser/attestation_switches.h"
 #include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_utils.h"
 #include "chrome/browser/enterprise/connectors/device_trust/attestation/common/proto/device_trust_attestation_ca.pb.h"
-#include "chrome/browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_switches.h"
 #include "chrome/browser/enterprise/connectors/device_trust/common/metrics_utils.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/browser/device_trust_key_manager_impl.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/browser/mock_device_trust_key_manager.h"
diff --git a/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.cc b/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.cc
index c477f5f6..194b99dc 100644
--- a/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.cc
@@ -4,22 +4,45 @@
 
 #include "chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.h"
 
+#include <string>
+#include <utility>
+
+#include "chrome/browser/enterprise/connectors/device_trust/device_trust_features.h"
 #include "chrome/browser/enterprise/connectors/device_trust/prefs.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 
 namespace enterprise_connectors {
 
+namespace {
+
+std::string ToPrefName(DTCPolicyLevel policy_level) {
+  switch (policy_level) {
+    case DTCPolicyLevel::kBrowser:
+      return kBrowserContextAwareAccessSignalsAllowlistPref;
+    case DTCPolicyLevel::kUser:
+      return kUserContextAwareAccessSignalsAllowlistPref;
+  }
+}
+
+}  // namespace
+
 FakeDeviceTrustConnectorService::FakeDeviceTrustConnectorService(
     sync_preferences::TestingPrefServiceSyncable* profile_prefs)
     : DeviceTrustConnectorService(profile_prefs), test_prefs_(profile_prefs) {}
 
 FakeDeviceTrustConnectorService::~FakeDeviceTrustConnectorService() = default;
 
-void FakeDeviceTrustConnectorService::update_policy(
-    base::Value::List new_urls) {
-  test_prefs_->SetManagedPref(kContextAwareAccessSignalsAllowlistPref,
-                              base::Value(std::move(new_urls)));
+void FakeDeviceTrustConnectorService::UpdateInlinePolicy(
+    base::Value::List new_urls,
+    DTCPolicyLevel policy_level) {
+  if (IsUserInlineFlowFeatureEnabled()) {
+    test_prefs_->SetManagedPref(ToPrefName(policy_level),
+                                base::Value(std::move(new_urls)));
+  } else {
+    test_prefs_->SetManagedPref(kContextAwareAccessSignalsAllowlistPref,
+                                base::Value(std::move(new_urls)));
+  }
 }
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.h b/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.h
index 52ad884..ecff97e 100644
--- a/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.h
+++ b/chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/values.h"
+#include "chrome/browser/enterprise/connectors/device_trust/common/common_types.h"
 #include "chrome/browser/enterprise/connectors/device_trust/device_trust_connector_service.h"
 
 namespace sync_preferences {
@@ -21,7 +22,8 @@
       sync_preferences::TestingPrefServiceSyncable* profile_prefs);
   ~FakeDeviceTrustConnectorService() override;
 
-  void update_policy(base::Value::List new_urls);
+  void UpdateInlinePolicy(base::Value::List new_urls,
+                          DTCPolicyLevel policy_level);
 
  private:
   raw_ptr<sync_preferences::TestingPrefServiceSyncable> test_prefs_;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
index ab0e8e8..d7a4e21f 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
@@ -11,8 +11,8 @@
 #include <string>
 #include <vector>
 
+#include "base/apple/bridging.h"
 #include "base/containers/span.h"
-#include "base/mac/bridging.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_piece.h"
@@ -103,7 +103,7 @@
   CFDictionarySetValue(attributes, kSecAttrTokenID,
                        kSecAttrTokenIDSecureEnclave);
   CFDictionarySetValue(attributes, kSecAttrKeySizeInBits,
-                       base::mac::NSToCFPtrCast(@256));
+                       base::apple::NSToCFPtrCast(@256));
   CFDictionarySetValue(
       attributes, kSecAttrLabel,
       base::SysUTF8ToCFStringRef(constants::kDeviceTrustSigningKeyLabel));
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
index cbb4ad33..32d3492 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
@@ -9,8 +9,8 @@
 
 #include <memory>
 
+#include "base/apple/bridging.h"
 #include "base/containers/span.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/strings/sys_string_conversions.h"
@@ -67,7 +67,7 @@
     CFDictionarySetValue(test_attributes, kSecAttrKeyType,
                          kSecAttrKeyTypeECSECPrimeRandom);
     CFDictionarySetValue(test_attributes, kSecAttrKeySizeInBits,
-                         base::mac::NSToCFPtrCast(@256));
+                         base::apple::NSToCFPtrCast(@256));
     base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
         CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                   &kCFTypeDictionaryKeyCallBacks,
@@ -121,7 +121,7 @@
         EXPECT_TRUE(CFEqual(kSecAttrTokenIDSecureEnclave,
                             base::mac::GetValueFromDictionary<CFStringRef>(
                                 attributes, kSecAttrTokenID)));
-        EXPECT_TRUE(CFEqual(base::mac::NSToCFPtrCast(@256),
+        EXPECT_TRUE(CFEqual(base::apple::NSToCFPtrCast(@256),
                             base::mac::GetValueFromDictionary<CFNumberRef>(
                                 attributes, kSecAttrKeySizeInBits)));
         auto* private_key_attributes =
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm
index eaa9d56..3763d96 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm
@@ -10,7 +10,7 @@
 
 #include <memory>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/scoped_cftyperef.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -49,7 +49,7 @@
 bool SecureEnclaveHelperImpl::IsSecureEnclaveSupported() {
   TKTokenWatcher* token_watcher = [[TKTokenWatcher alloc] init];
   return ([token_watcher.tokenIDs
-      containsObject:base::mac::CFToNSPtrCast(kSecAttrTokenIDSecureEnclave)]);
+      containsObject:base::apple::CFToNSPtrCast(kSecAttrTokenIDSecureEnclave)]);
 }
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
index a8599efc5..bebc0693 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
@@ -12,8 +12,8 @@
 #include <utility>
 #include <vector>
 
+#include "base/apple/bridging.h"
 #include "base/containers/span.h"
-#include "base/mac/bridging.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h"
@@ -54,7 +54,7 @@
     CFDictionarySetValue(test_attributes, kSecAttrKeyType,
                          kSecAttrKeyTypeECSECPrimeRandom);
     CFDictionarySetValue(test_attributes, kSecAttrKeySizeInBits,
-                         base::mac::NSToCFPtrCast(@256));
+                         base::apple::NSToCFPtrCast(@256));
     base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
         CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                   &kCFTypeDictionaryKeyCallBacks,
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate_unittest.mm
index ea22515f..4b9da5f 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate_unittest.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate_unittest.mm
@@ -10,8 +10,8 @@
 #include <string>
 #include <utility>
 
+#include "base/apple/bridging.h"
 #include "base/containers/span.h"
-#include "base/mac/bridging.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h"
@@ -65,7 +65,7 @@
     CFDictionarySetValue(test_attributes, kSecAttrKeyType,
                          kSecAttrKeyTypeECSECPrimeRandom);
     CFDictionarySetValue(test_attributes, kSecAttrKeySizeInBits,
-                         base::mac::NSToCFPtrCast(@256));
+                         base::apple::NSToCFPtrCast(@256));
     base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
         CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                   &kCFTypeDictionaryKeyCallBacks,
diff --git a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc
index 67b2f91..2be63c64 100644
--- a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc
@@ -89,7 +89,10 @@
     fake_connector_ = std::make_unique<FakeDeviceTrustConnectorService>(
         profile_.GetTestingPrefService());
 
-    fake_connector_->update_policy(GetTrustedUrls());
+    fake_connector_->UpdateInlinePolicy(GetTrustedUrls(),
+                                        DTCPolicyLevel::kBrowser);
+    fake_connector_->UpdateInlinePolicy(GetTrustedUrls(),
+                                        DTCPolicyLevel::kUser);
 
     EXPECT_CALL(mock_device_trust_service_, Watches(_))
         .WillRepeatedly(Invoke(
diff --git a/chrome/browser/enterprise/profile_management/saml_response_parser.cc b/chrome/browser/enterprise/profile_management/saml_response_parser.cc
new file mode 100644
index 0000000..adb3d01
--- /dev/null
+++ b/chrome/browser/enterprise/profile_management/saml_response_parser.cc
@@ -0,0 +1,198 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/profile_management/saml_response_parser.h"
+
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/containers/flat_map.h"
+#include "base/task/sequenced_task_runner.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "services/data_decoder/public/cpp/data_decoder.h"
+
+namespace profile_management {
+
+namespace {
+constexpr char kChildrenKey[] = "children";
+
+// Finds the value of the attribute named SAMLResponse from `dict` by doing a
+// depth-first search in `dict`.
+// Returns a pointer to the value or nullptr if nothing was found.
+const std::string* GetSamlResponseFromDict(const base::Value::Dict& dict) {
+  const std::string* attribute_name =
+      dict.FindStringByDottedPath("attributes.name");
+  if (attribute_name && *attribute_name == "SAMLResponse") {
+    return dict.FindStringByDottedPath("attributes.value");
+  }
+  const base::Value::List* children = dict.FindList(kChildrenKey);
+  if (!children) {
+    return nullptr;
+  }
+  for (const auto& child : *children) {
+    if (!child.is_dict()) {
+      continue;
+    }
+    const auto* child_saml_response = GetSamlResponseFromDict(child.GetDict());
+    if (child_saml_response) {
+      return child_saml_response;
+    }
+  }
+  return nullptr;
+}
+
+// Finds the value of `attribute` from `dict` by doing a depth-first search in
+// `dict`. First we find an attribute with "Name" == `attribute`, then we try
+// to find a children of that attribute with the tag AttributeValue. That
+// attribute itself has children, and we look for the value the children with a
+// "text" key.
+// Returns a pointer to the value or nullptr if nothing was found.
+const std::string* GetAttributeValue(const base::Value::Dict& dict,
+                                     const std::string& attribute) {
+  const std::string* attribute_name =
+      dict.FindStringByDottedPath("attributes.Name");
+  if (attribute_name && *attribute_name == attribute) {
+    const base::Value::List* attribute_children = dict.FindList(kChildrenKey);
+    if (!attribute_children) {
+      return nullptr;
+    }
+    for (const auto& attribute_child : *attribute_children) {
+      const std::string* tag = attribute_child.FindStringPath("tag");
+      if (!tag || *tag != "AttributeValue") {
+        continue;
+      }
+
+      const base::Value* attribute_value_children =
+          attribute_child.FindListPath(kChildrenKey);
+      if (!attribute_value_children) {
+        continue;
+      }
+      for (const auto& attribute_value_item :
+           attribute_value_children->GetList()) {
+        if (attribute_value_item.is_dict()) {
+          return attribute_value_item.FindStringPath("text");
+        }
+      }
+    }
+  }
+
+  const base::Value::List* children = dict.FindList(kChildrenKey);
+  if (!children) {
+    return nullptr;
+  }
+  for (const auto& child : *children) {
+    if (!child.is_dict()) {
+      continue;
+    }
+    const std::string* value = GetAttributeValue(child.GetDict(), attribute);
+    if (value) {
+      return value;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace
+
+SAMLResponseParser::SAMLResponseParser(
+    std::vector<std::string>&& attributes,
+    const mojo::DataPipeConsumerHandle& body,
+    base::OnceCallback<void(base::flat_map<std::string, std::string>)> callback)
+    : attributes_(std::move(attributes)),
+      body_(body),
+      body_consumer_watcher_(FROM_HERE,
+                             mojo::SimpleWatcher::ArmingPolicy::MANUAL,
+                             base::SequencedTaskRunner::GetCurrentDefault()),
+      callback_(std::move(callback)) {
+  body_consumer_watcher_.Watch(
+      body_, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+      base::BindRepeating(&SAMLResponseParser::OnBodyReady,
+                          weak_ptr_factory_.GetWeakPtr()));
+  body_consumer_watcher_.ArmOrNotify();
+}
+
+SAMLResponseParser::~SAMLResponseParser() = default;
+
+void SAMLResponseParser::OnBodyReady(MojoResult) {
+  uint32_t num_bytes = 0;
+  MojoResult result =
+      body_.ReadData(nullptr, &num_bytes, MOJO_READ_DATA_FLAG_QUERY);
+  switch (result) {
+    case MOJO_RESULT_OK: {
+      std::string response(num_bytes, '\0');
+      body_.ReadData(response.data(), &num_bytes, MOJO_READ_DATA_FLAG_PEEK);
+      data_decoder::DataDecoder::ParseXmlIsolated(
+          response,
+          data_decoder::mojom::XmlParser::WhitespaceBehavior::
+              kPreserveSignificant,
+          base::BindOnce(&SAMLResponseParser::GetSamlResponse,
+                         weak_ptr_factory_.GetWeakPtr()));
+      break;
+    }
+    case MOJO_RESULT_FAILED_PRECONDITION:
+      DCHECK_EQ(num_bytes, 0u);
+      break;
+    case MOJO_RESULT_SHOULD_WAIT:
+      body_consumer_watcher_.ArmOrNotify();
+      return;
+    default:
+      NOTREACHED();
+      return;
+  }
+
+  // Stop watching for response body changes.
+  body_consumer_watcher_.Cancel();
+}
+
+void SAMLResponseParser::GetSamlResponse(
+    data_decoder::DataDecoder::ValueOrError value_or_error) {
+  if (!value_or_error.has_value() || !value_or_error.value().is_dict()) {
+    std::move(callback_).Run(base::flat_map<std::string, std::string>());
+    return;
+  }
+
+  const auto* saml_response =
+      GetSamlResponseFromDict(value_or_error.value().GetDict());
+
+  if (!saml_response) {
+    std::move(callback_).Run(base::flat_map<std::string, std::string>());
+    return;
+  }
+
+  std::string decoded_response_value;
+  if (!base::Base64Decode(*saml_response, &decoded_response_value)) {
+    std::move(callback_).Run(base::flat_map<std::string, std::string>());
+    return;
+  }
+
+  data_decoder::DataDecoder::ParseXmlIsolated(
+      decoded_response_value,
+      data_decoder::mojom::XmlParser::WhitespaceBehavior::kPreserveSignificant,
+      base::BindOnce(&SAMLResponseParser::GetAttributesFromSAMLResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SAMLResponseParser::GetAttributesFromSAMLResponse(
+    data_decoder::DataDecoder::ValueOrError value_or_error) {
+  base::flat_map<std::string, std::string> result;
+  if (!value_or_error.has_value() || !value_or_error.value().is_dict()) {
+    std::move(callback_).Run(std::move(result));
+    return;
+  }
+
+  for (const auto& attribute : attributes_) {
+    const auto* value =
+        GetAttributeValue(value_or_error.value().GetDict(), attribute);
+    if (value) {
+      result[attribute] = *value;
+    }
+  }
+
+  std::move(callback_).Run(std::move(result));
+}
+
+}  // namespace profile_management
diff --git a/chrome/browser/enterprise/profile_management/saml_response_parser.h b/chrome/browser/enterprise/profile_management/saml_response_parser.h
new file mode 100644
index 0000000..8d0885b
--- /dev/null
+++ b/chrome/browser/enterprise/profile_management/saml_response_parser.h
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef CHROME_BROWSER_ENTERPRISE_PROFILE_MANAGEMENT_SAML_RESPONSE_PARSER_H_
+#define CHROME_BROWSER_ENTERPRISE_PROFILE_MANAGEMENT_SAML_RESPONSE_PARSER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/containers/flat_map.h"
+#include "base/functional/callback.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "services/data_decoder/public/cpp/data_decoder.h"
+
+namespace profile_management {
+
+// Utility class that retrieves attributes from a SAML response found in the
+// body of a web page.
+class SAMLResponseParser {
+ public:
+  // Decode SAML response from web request `body` and retrieve `attributes` from
+  // the response. Invoke `callback` with a map the attributes values.
+  SAMLResponseParser(
+      std::vector<std::string>&& attributes,
+      const mojo::DataPipeConsumerHandle& body,
+      base::OnceCallback<void(base::flat_map<std::string, std::string>)>
+          callback);
+  SAMLResponseParser(const SAMLResponseParser&) = delete;
+  SAMLResponseParser& operator=(const SAMLResponseParser&) = delete;
+  ~SAMLResponseParser();
+
+ private:
+  void OnBodyReady(MojoResult result);
+  void GetSamlResponse(data_decoder::DataDecoder::ValueOrError value_or_error);
+
+  void GetAttributesFromSAMLResponse(
+      data_decoder::DataDecoder::ValueOrError value_or_error);
+
+  std::vector<std::string> attributes_;
+  const mojo::DataPipeConsumerHandle& body_;
+  mojo::SimpleWatcher body_consumer_watcher_;
+  base::OnceCallback<void(base::flat_map<std::string, std::string>)> callback_;
+  base::WeakPtrFactory<SAMLResponseParser> weak_ptr_factory_{this};
+};
+
+}  // namespace profile_management
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PROFILE_MANAGEMENT_SAML_RESPONSE_PARSER_H_
diff --git a/chrome/browser/enterprise/profile_management/saml_response_parser_unittest.cc b/chrome/browser/enterprise/profile_management/saml_response_parser_unittest.cc
new file mode 100644
index 0000000..0f264b1d
--- /dev/null
+++ b/chrome/browser/enterprise/profile_management/saml_response_parser_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/profile_management/saml_response_parser.h"
+
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/containers/flat_map.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace profile_management {
+
+using testing::_;
+
+namespace {
+const char kValidSAMLResponse[] = R"(
+<samlp:Response ID="id" Version="2.0">
+  <Issuer xmlns="urn:oasis:names:tc:SAML:2.0">https://issuer.com/</Issuer>
+  <samlp:Status>
+    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+  </samlp:Status>
+  <Assertion ID="session" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0">
+    <Issuer>https://issuer.com/</Issuer>
+    <Subject>
+      <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
+user@domain.com
+      </NameID>
+    </Subject>
+    <AttributeStatement>
+      <Attribute Name="attribute">
+        <AttributeValue>attributevalue</AttributeValue>
+      </Attribute>
+      <Attribute Name="anotherattribute">
+        <AttributeValue>anotherattributevalue</AttributeValue>
+      </Attribute>
+      <Attribute Name="uselessattribute">
+        <AttributeValue>uselessattributevalue</AttributeValue>
+      </Attribute>
+    </AttributeStatement>
+  </Assertion>
+</samlp:Response>)";
+
+const char kHTMLTemplate[] = R"(
+<html>
+  <body>
+    <form method="POST" name="hiddenform">
+      <input type="hidden" name="SAMLResponse" value="%s"/>
+    </form>
+  </body>
+</html>)";
+
+}  // namespace
+
+class SAMLResponseParserTest : public BrowserWithTestWindowTest {
+ public:
+  SAMLResponseParserTest() = default;
+
+  ~SAMLResponseParserTest() override = default;
+
+  void SetUp() override { BrowserWithTestWindowTest::SetUp(); }
+
+ protected:
+  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
+  mojo::ScopedDataPipeProducerHandle producer_handle_;
+  mojo::ScopedDataPipeConsumerHandle consumer_handle_;
+};
+
+TEST_F(SAMLResponseParserTest, RetrievesNoAttributesWithEmptyResponse) {
+  std::string response = "<html></html>";
+  uint32_t write_size = response.size();
+  ASSERT_EQ(
+      mojo::CreateDataPipe(response.size(), producer_handle_, consumer_handle_),
+      MOJO_RESULT_OK);
+  base::flat_map<std::string, std::string> attributes;
+
+  base::RunLoop loop;
+  SAMLResponseParser body_reader(
+      std::vector<std::string>{"attribute", "notattribute", "anotherattribute"},
+      consumer_handle_.get(),
+      base::BindLambdaForTesting(
+          [&attributes,
+           &loop](base::flat_map<std::string, std::string> result) {
+            attributes = std::move(result);
+            loop.Quit();
+          }));
+  ASSERT_EQ(producer_handle_->WriteData(response.c_str(), &write_size,
+                                        MOJO_WRITE_DATA_FLAG_NONE),
+            MOJO_RESULT_OK);
+  loop.Run();
+
+  EXPECT_TRUE(attributes.empty());
+}
+
+TEST_F(SAMLResponseParserTest, RetrievesNoAttributesWithEmptySAMLResponse) {
+  std::string response = base::StringPrintf(kHTMLTemplate, "");
+  uint32_t write_size = response.size();
+  ASSERT_EQ(
+      mojo::CreateDataPipe(response.size(), producer_handle_, consumer_handle_),
+      MOJO_RESULT_OK);
+  base::flat_map<std::string, std::string> attributes;
+
+  base::RunLoop loop;
+  SAMLResponseParser body_reader(
+      std::vector<std::string>{"attribute", "notattribute", "anotherattribute"},
+      consumer_handle_.get(),
+      base::BindLambdaForTesting(
+          [&attributes,
+           &loop](base::flat_map<std::string, std::string> result) {
+            attributes = std::move(result);
+            loop.Quit();
+          }));
+  ASSERT_EQ(producer_handle_->WriteData(response.c_str(), &write_size,
+                                        MOJO_WRITE_DATA_FLAG_NONE),
+            MOJO_RESULT_OK);
+  loop.Run();
+
+  EXPECT_TRUE(attributes.empty());
+}
+
+TEST_F(SAMLResponseParserTest, RetrievesSpecifiedAttributesWithValidResponse) {
+  std::string encoded_saml_response;
+  base::Base64Encode(kValidSAMLResponse, &encoded_saml_response);
+  std::string response =
+      base::StringPrintf(kHTMLTemplate, encoded_saml_response.c_str());
+  uint32_t write_size = response.size();
+  ASSERT_EQ(
+      mojo::CreateDataPipe(response.size(), producer_handle_, consumer_handle_),
+      MOJO_RESULT_OK);
+  base::flat_map<std::string, std::string> attributes;
+
+  base::RunLoop loop;
+  SAMLResponseParser body_reader(
+      std::vector<std::string>{"attribute", "notattribute", "anotherattribute"},
+      consumer_handle_.get(),
+      base::BindLambdaForTesting(
+          [&attributes,
+           &loop](base::flat_map<std::string, std::string> result) {
+            attributes = std::move(result);
+            loop.Quit();
+          }));
+  ASSERT_EQ(producer_handle_->WriteData(response.c_str(), &write_size,
+                                        MOJO_WRITE_DATA_FLAG_NONE),
+            MOJO_RESULT_OK);
+  loop.Run();
+
+  EXPECT_EQ(attributes["attribute"], "attributevalue");
+  EXPECT_EQ(attributes["anotherattribute"], "anotherattributevalue");
+  EXPECT_EQ(attributes.find("notattribute"), attributes.end());
+  EXPECT_EQ(attributes.find("uselessattribute"), attributes.end());
+}
+
+}  // namespace profile_management
diff --git a/chrome/browser/enterprise/signals/user_delegate_impl.cc b/chrome/browser/enterprise/signals/user_delegate_impl.cc
index 2498c72..8690122e 100644
--- a/chrome/browser/enterprise/signals/user_delegate_impl.cc
+++ b/chrome/browser/enterprise/signals/user_delegate_impl.cc
@@ -7,6 +7,8 @@
 #include <set>
 
 #include "base/check.h"
+#include "chrome/browser/enterprise/connectors/device_trust/common/common_types.h"
+#include "chrome/browser/enterprise/connectors/device_trust/device_trust_connector_service.h"
 #include "chrome/browser/enterprise/util/affiliation.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
@@ -16,11 +18,31 @@
 
 namespace enterprise_signals {
 
-UserDelegateImpl::UserDelegateImpl(Profile* profile,
-                                   signin::IdentityManager* identity_manager)
-    : profile_(profile), identity_manager_(identity_manager) {
-  DCHECK(profile_);
-  DCHECK(identity_manager_);
+using DTCPolicyLevel = enterprise_connectors::DTCPolicyLevel;
+
+namespace {
+
+policy::PolicyScope ToPolicyScope(DTCPolicyLevel policy_level) {
+  switch (policy_level) {
+    case DTCPolicyLevel::kUser:
+      return policy::POLICY_SCOPE_USER;
+    case DTCPolicyLevel::kBrowser:
+      return policy::POLICY_SCOPE_MACHINE;
+  }
+}
+
+}  // namespace
+
+UserDelegateImpl::UserDelegateImpl(
+    Profile* profile,
+    signin::IdentityManager* identity_manager,
+    enterprise_connectors::DeviceTrustConnectorService*
+        device_trust_connector_service)
+    : profile_(profile),
+      identity_manager_(identity_manager),
+      device_trust_connector_service_(device_trust_connector_service) {
+  CHECK(profile_);
+  CHECK(identity_manager_);
 }
 
 UserDelegateImpl::~UserDelegateImpl() = default;
@@ -47,8 +69,14 @@
 
 std::set<policy::PolicyScope> UserDelegateImpl::GetPolicyScopesNeedingSignals()
     const {
-  // TODO(b:279060607): Add actual policy scopes.
-  return std::set<policy::PolicyScope>();
+  std::set<policy::PolicyScope> policy_scopes;
+  if (device_trust_connector_service_) {
+    for (const auto policy_level :
+         device_trust_connector_service_->GetEnabledInlinePolicyLevels()) {
+      policy_scopes.insert(ToPolicyScope(policy_level));
+    }
+  }
+  return policy_scopes;
 }
 
 }  // namespace enterprise_signals
diff --git a/chrome/browser/enterprise/signals/user_delegate_impl.h b/chrome/browser/enterprise/signals/user_delegate_impl.h
index 7eb74c2..9939bf4 100644
--- a/chrome/browser/enterprise/signals/user_delegate_impl.h
+++ b/chrome/browser/enterprise/signals/user_delegate_impl.h
@@ -10,6 +10,10 @@
 
 class Profile;
 
+namespace enterprise_connectors {
+class DeviceTrustConnectorService;
+}  // namespace enterprise_connectors
+
 namespace signin {
 class IdentityManager;
 }  // namespace signin
@@ -18,7 +22,10 @@
 
 class UserDelegateImpl : public device_signals::UserDelegate {
  public:
-  UserDelegateImpl(Profile* profile, signin::IdentityManager* identity_manager);
+  UserDelegateImpl(Profile* profile,
+                   signin::IdentityManager* identity_manager,
+                   enterprise_connectors::DeviceTrustConnectorService*
+                       device_trust_connector_service);
   ~UserDelegateImpl() override;
 
   UserDelegateImpl(const UserDelegateImpl&) = delete;
@@ -33,6 +40,12 @@
  private:
   const raw_ptr<Profile> profile_;
   const raw_ptr<signin::IdentityManager> identity_manager_;
+
+  // The connector service in charge of giving information about whether the
+  // Device Trust connector is enabled or not. Can be nullptr if the
+  // browser/profile is in an unsupported context (e.g. incognito).
+  const raw_ptr<enterprise_connectors::DeviceTrustConnectorService>
+      device_trust_connector_service_;
 };
 
 }  // namespace enterprise_signals
diff --git a/chrome/browser/enterprise/signals/user_delegate_impl_unittest.cc b/chrome/browser/enterprise/signals/user_delegate_impl_unittest.cc
index a7ee91d..99c91b065 100644
--- a/chrome/browser/enterprise/signals/user_delegate_impl_unittest.cc
+++ b/chrome/browser/enterprise/signals/user_delegate_impl_unittest.cc
@@ -6,109 +6,187 @@
 
 #include <set>
 
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/enterprise/connectors/device_trust/device_trust_features.h"
+#include "chrome/browser/enterprise/connectors/device_trust/fake_device_trust_connector_service.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/policy/core/common/policy_types.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace enterprise_signals {
 
+using DTCPolicyLevel = enterprise_connectors::DTCPolicyLevel;
+
 namespace {
+
 constexpr char kUserEmail[] = "someEmail@example.com";
 constexpr char kOtherUserEmail[] = "someOtherUser@example.com";
 constexpr char kOtherUserGaiaId[] = "some-other-user-gaia";
+
+base::Value::List GetUrls() {
+  base::Value::List trusted_urls;
+  trusted_urls.Append("https://www.example.com");
+  trusted_urls.Append("example2.example.com");
+  return trusted_urls;
+}
+
 }  // namespace
 
 class UserDelegateImplTest : public testing::Test {
  protected:
-  std::unique_ptr<TestingProfile> CreateProfile(bool is_managed) {
+  void CreateDelegate(bool is_managed_user = true) {
     TestingProfile::Builder builder;
-    builder.OverridePolicyConnectorIsManagedForTesting(is_managed);
-    return builder.Build();
+    builder.OverridePolicyConnectorIsManagedForTesting(is_managed_user);
+    testing_profile_ = builder.Build();
+
+    fake_dt_connector_service_ = std::make_unique<
+        enterprise_connectors::FakeDeviceTrustConnectorService>(
+        testing_profile_->GetTestingPrefService());
+
+    user_delegate_ = std::make_unique<UserDelegateImpl>(
+        testing_profile_.get(), identity_test_env_.identity_manager(),
+        fake_dt_connector_service_.get());
   }
 
   content::BrowserTaskEnvironment task_environment_;
   signin::IdentityTestEnvironment identity_test_env_;
+  std::unique_ptr<TestingProfile> testing_profile_;
+  std::unique_ptr<enterprise_connectors::FakeDeviceTrustConnectorService>
+      fake_dt_connector_service_;
+
+  std::unique_ptr<UserDelegateImpl> user_delegate_;
 };
 
 // Tests that IsManagedUser returns false when the user is not managed.
 TEST_F(UserDelegateImplTest, IsManagedUser_False) {
-  auto test_profile = CreateProfile(/*is_managed=*/false);
-
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_FALSE(user_delegate.IsManagedUser());
+  CreateDelegate(/*is_managed_user=*/false);
+  EXPECT_FALSE(user_delegate_->IsManagedUser());
 }
 
 // Tests that IsManagedUser returns true when the user is managed.
 TEST_F(UserDelegateImplTest, IsManagedUser_True) {
-  auto test_profile = CreateProfile(/*is_managed=*/true);
-
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_TRUE(user_delegate.IsManagedUser());
+  CreateDelegate();
+  EXPECT_TRUE(user_delegate_->IsManagedUser());
 }
 
 // Tests that IsSameUser returns false when given a different user.
 TEST_F(UserDelegateImplTest, IsSameManagedUser_DifferentUser) {
-  auto test_profile = CreateProfile(/*is_managed=*/true);
   auto account = identity_test_env_.MakePrimaryAccountAvailable(
       kUserEmail, signin::ConsentLevel::kSignin);
   auto other_account = identity_test_env_.MakeAccountAvailableWithCookies(
       kOtherUserEmail, kOtherUserGaiaId);
 
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_FALSE(user_delegate.IsSameUser(kOtherUserGaiaId));
+  CreateDelegate();
+  EXPECT_FALSE(user_delegate_->IsSameUser(kOtherUserGaiaId));
 }
 
 // Tests that IsSameUser returns false when there is no primary user.
 TEST_F(UserDelegateImplTest, IsSameUser_NoPrimaryUser) {
-  auto test_profile = CreateProfile(/*is_managed=*/true);
   auto other_account = identity_test_env_.MakeAccountAvailableWithCookies(
       kOtherUserEmail, kOtherUserGaiaId);
 
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_FALSE(user_delegate.IsSameUser(kOtherUserGaiaId));
+  CreateDelegate();
+  EXPECT_FALSE(user_delegate_->IsSameUser(kOtherUserGaiaId));
 }
 
 // Tests that IsSameUser returns true when given the same user, and the
 // user did not give Sync consent.
 TEST_F(UserDelegateImplTest, IsSameUser_SameUser_Signin) {
-  auto test_profile = CreateProfile(/*is_managed=*/true);
   auto account = identity_test_env_.MakePrimaryAccountAvailable(
       kUserEmail, signin::ConsentLevel::kSignin);
 
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_TRUE(user_delegate.IsSameUser(account.gaia));
+  CreateDelegate();
+  EXPECT_TRUE(user_delegate_->IsSameUser(account.gaia));
 }
 
 // Tests that IsSameUser returns true when given the same user, and the
 // user gave Sync consent.
 TEST_F(UserDelegateImplTest, IsSameUser_SameUser_Sync) {
-  auto test_profile = CreateProfile(/*is_managed=*/true);
   auto account = identity_test_env_.MakePrimaryAccountAvailable(
       kUserEmail, signin::ConsentLevel::kSync);
 
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_TRUE(user_delegate.IsSameUser(account.gaia));
+  CreateDelegate();
+  EXPECT_TRUE(user_delegate_->IsSameUser(account.gaia));
 }
 
-// Tests that GetPolicyScopesNeedingSignals only returns an empty set
-// for now.
+// Tests that GetPolicyScopesNeedingSignals returns an empty set when
+// no policies are enabled.
 TEST_F(UserDelegateImplTest, GetPolicyScopesNeedingSignals_Empty) {
-  auto test_profile = CreateProfile(/*is_managed=*/true);
-  UserDelegateImpl user_delegate(test_profile.get(),
-                                 identity_test_env_.identity_manager());
-  EXPECT_EQ(user_delegate.GetPolicyScopesNeedingSignals(),
+  CreateDelegate();
+  EXPECT_EQ(user_delegate_->GetPolicyScopesNeedingSignals(),
             std::set<policy::PolicyScope>());
 }
 
+class UserDelegateImplFlagsTest : public UserDelegateImplTest,
+                                  public testing::WithParamInterface<bool> {
+ protected:
+  UserDelegateImplFlagsTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        enterprise_connectors::kUserDTCInlineFlowEnabled,
+        is_new_flag_enabled());
+  }
+
+  bool is_new_flag_enabled() const { return GetParam(); }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests what GetPolicyScopesNeedingSignals returns when the policy is enabled
+// at the user level.
+TEST_P(UserDelegateImplFlagsTest, GetPolicyScopesNeedingSignals_User) {
+  CreateDelegate();
+  fake_dt_connector_service_->UpdateInlinePolicy(GetUrls(),
+                                                 DTCPolicyLevel::kUser);
+
+  const auto& policy_scopes = user_delegate_->GetPolicyScopesNeedingSignals();
+
+  std::set<policy::PolicyScope> expected_scopes = {policy::POLICY_SCOPE_USER};
+  if (!is_new_flag_enabled()) {
+    expected_scopes.insert(policy::POLICY_SCOPE_MACHINE);
+  }
+  EXPECT_EQ(policy_scopes, expected_scopes);
+}
+
+// Tests what GetPolicyScopesNeedingSignals returns when the policy is enabled
+// at the browser level.
+TEST_P(UserDelegateImplFlagsTest, GetPolicyScopesNeedingSignals_Browser) {
+  CreateDelegate();
+  fake_dt_connector_service_->UpdateInlinePolicy(GetUrls(),
+                                                 DTCPolicyLevel::kBrowser);
+
+  const auto& policy_scopes = user_delegate_->GetPolicyScopesNeedingSignals();
+
+  std::set<policy::PolicyScope> expected_scopes = {
+      policy::POLICY_SCOPE_MACHINE};
+  if (!is_new_flag_enabled()) {
+    expected_scopes.insert(policy::POLICY_SCOPE_USER);
+  }
+  EXPECT_EQ(policy_scopes, expected_scopes);
+}
+
+// Tests what GetPolicyScopesNeedingSignals returns when the policy is enabled
+// at the both the user and browser levels.
+TEST_P(UserDelegateImplFlagsTest,
+       GetPolicyScopesNeedingSignals_UserAndBrowser) {
+  CreateDelegate();
+  fake_dt_connector_service_->UpdateInlinePolicy(GetUrls(),
+                                                 DTCPolicyLevel::kBrowser);
+  fake_dt_connector_service_->UpdateInlinePolicy(GetUrls(),
+                                                 DTCPolicyLevel::kUser);
+
+  const auto& policy_scopes = user_delegate_->GetPolicyScopesNeedingSignals();
+
+  std::set<policy::PolicyScope> expected_scopes = {policy::POLICY_SCOPE_MACHINE,
+                                                   policy::POLICY_SCOPE_USER};
+  EXPECT_EQ(policy_scopes, expected_scopes);
+}
+
+INSTANTIATE_TEST_SUITE_P(, UserDelegateImplFlagsTest, testing::Bool());
+
 }  // namespace enterprise_signals
diff --git a/chrome/browser/enterprise/signals/user_permission_service_factory.cc b/chrome/browser/enterprise/signals/user_permission_service_factory.cc
index 6a42e0b..56eddc64 100644
--- a/chrome/browser/enterprise/signals/user_permission_service_factory.cc
+++ b/chrome/browser/enterprise/signals/user_permission_service_factory.cc
@@ -62,14 +62,16 @@
 
   auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
 
-  auto* user_permission_service = new device_signals::UserPermissionServiceImpl(
-      management_service,
-      std::make_unique<UserDelegateImpl>(profile, identity_manager),
-      profile->GetPrefs());
-
   auto* device_trust_connector_service =
       enterprise_connectors::DeviceTrustConnectorServiceFactory::GetForProfile(
           profile);
+
+  auto* user_permission_service = new device_signals::UserPermissionServiceImpl(
+      management_service,
+      std::make_unique<UserDelegateImpl>(profile, identity_manager,
+                                         device_trust_connector_service),
+      profile->GetPrefs());
+
   if (device_trust_connector_service) {
     device_trust_connector_service->AddObserver(
         std::make_unique<enterprise_connectors::ConsentPolicyObserver>(
diff --git a/chrome/browser/enterprise/util/managed_browser_utils_unittest.cc b/chrome/browser/enterprise/util/managed_browser_utils_unittest.cc
index a30ec42f..8b220c6 100644
--- a/chrome/browser/enterprise/util/managed_browser_utils_unittest.cc
+++ b/chrome/browser/enterprise/util/managed_browser_utils_unittest.cc
@@ -139,15 +139,11 @@
 
     base::Value::Dict filter;
     if (!issuer.empty()) {
-      base::Value issuer_value(base::Value::Type::DICT);
-      issuer_value.SetStringKey("CN", issuer);
-      filter.Set("ISSUER", std::move(issuer_value));
+      filter.Set("ISSUER", base::Value::Dict().Set("CN", issuer));
     }
 
     if (!subject.empty()) {
-      base::Value subject_value(base::Value::Type::DICT);
-      subject_value.SetStringKey("CN", subject);
-      filter.Set("SUBJECT", std::move(subject_value));
+      filter.Set("SUBJECT", base::Value::Dict().Set("CN", subject));
     }
 
     return filter;
diff --git a/chrome/browser/extensions/active_tab_permission_granter.h b/chrome/browser/extensions/active_tab_permission_granter.h
index f25d4ec..9da3708 100644
--- a/chrome/browser/extensions/active_tab_permission_granter.h
+++ b/chrome/browser/extensions/active_tab_permission_granter.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_ACTIVE_TAB_PERMISSION_GRANTER_H_
 #define CHROME_BROWSER_EXTENSIONS_ACTIVE_TAB_PERMISSION_GRANTER_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "extensions/browser/extension_registry.h"
diff --git a/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc b/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
index 1eea0cb0..6bd29e7 100644
--- a/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
+++ b/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
@@ -478,7 +478,6 @@
               context.autocapitalization_mode);
       private_api_input_context.spell_check =
           ConvertInputContextSpellCheck(context.spellcheck_mode);
-      private_api_input_context.has_been_password = false;
       private_api_input_context.should_do_learning =
           ConvertPersonalizationMode(context);
       private_api_input_context.focus_reason =
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils.cc b/chrome/browser/extensions/api/printing/printing_api_utils.cc
index dd39fc3..023cf8ea 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_utils.cc
@@ -54,7 +54,7 @@
   static const base::NoDestructor<
       base::flat_map<base::StringPiece, base::flat_set<base::StringPiece>>>
       kVendorItemAllowList({
-          {"label-mode-configured", {"cutter", "tear-off"}},
+          {"finishings", {"none", "trim"}},
       });
 
   const auto& item = kVendorItemAllowList->find(vendor_item.id);
@@ -140,6 +140,8 @@
       return idl::PRINTER_STATUS_OUTPUT_FULL;
     case chromeos::PrinterErrorCode::STOPPED:
       return idl::PRINTER_STATUS_STOPPED;
+    case chromeos::PrinterErrorCode::EXPIRED_CERTIFICATE:
+      return idl::PRINTER_STATUS_EXPIRED_CERTIFICATE;
     default:
       break;
   }
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc b/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc
index 0bdf6db..e3992329 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc
@@ -33,8 +33,8 @@
 constexpr int kMediaSizeWidth = 210000;
 constexpr int kMediaSizeHeight = 297000;
 constexpr char kMediaSizeVendorId[] = "iso_a4_210x297mm";
-constexpr char kVendorItemId[] = "label-mode-configured";
-constexpr char kVendorItemValue[] = "cutter";
+constexpr char kVendorItemId[] = "finishings";
+constexpr char kVendorItemValue[] = "trim";
 
 constexpr char kCjt[] = R"(
     {
@@ -63,8 +63,8 @@
         },
         "vendor_ticket_item": [
           {
-            "id": "label-mode-configured",
-            "value": "cutter"
+            "id": "finishings",
+            "value": "trim"
           }
         ],
         "collate": {
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
index 5b9d1742..72b99b2 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
@@ -23,8 +23,8 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
+#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h"
-#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h"
 #include "chrome/browser/ash/login/quick_unlock/auth_token.h"
 #include "chrome/browser/ash/login/quick_unlock/pin_backend.h"
 #include "chrome/browser/ash/login/quick_unlock/pin_storage_prefs.h"
@@ -86,7 +86,7 @@
 constexpr char kValidPassword[] = "valid";
 constexpr char kInvalidPassword[] = "invalid";
 
-class FakeEasyUnlockService : public ash::EasyUnlockServiceRegular {
+class FakeEasyUnlockService : public ash::EasyUnlockService {
  public:
   FakeEasyUnlockService(
       Profile* profile,
@@ -94,19 +94,15 @@
       ash::secure_channel::FakeSecureChannelClient* fake_secure_channel_client,
       ash::multidevice_setup::FakeMultiDeviceSetupClient*
           fake_multidevice_setup_client)
-      : ash::EasyUnlockServiceRegular(profile,
-                                      fake_secure_channel_client,
-                                      fake_device_sync_client,
-                                      fake_multidevice_setup_client) {}
+      : ash::EasyUnlockService(profile,
+                               fake_secure_channel_client,
+                               fake_device_sync_client,
+                               fake_multidevice_setup_client) {}
 
   FakeEasyUnlockService(const FakeEasyUnlockService&) = delete;
   FakeEasyUnlockService& operator=(const FakeEasyUnlockService&) = delete;
 
   ~FakeEasyUnlockService() override {}
-
-  // ash::EasyUnlockServiceRegular:
-  void InitializeInternal() override {}
-  void ShutdownInternal() override {}
 };
 
 std::unique_ptr<KeyedService> CreateEasyUnlockServiceForTest(
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 426b7c2..861457c 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -956,6 +956,8 @@
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[ash::prefs::kUserMicrophoneAllowed] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
+  (*s_allowlist)[ash::prefs::kUserSpeakOnMuteDetectionEnabled] =
+      settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[ash::prefs::kUserGeolocationAllowed] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
 #else
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index e6070f4..3bbede9 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -394,6 +394,8 @@
 
   const chromeos::WindowPinType previous_type =
       window->GetProperty(lacros::kWindowPinTypeKey);
+  CHECK_NE(previous_type, chromeos::WindowPinType::kPinned)
+      << "Extensions only set Trusted Pinned";
 
   bool previous_pinned =
       previous_type == chromeos::WindowPinType::kTrustedPinned;
diff --git a/chrome/browser/extensions/extension_action_runner.h b/chrome/browser/extensions/extension_action_runner.h
index de9f501..b89ed0e 100644
--- a/chrome/browser/extensions/extension_action_runner.h
+++ b/chrome/browser/extensions/extension_action_runner.h
@@ -13,6 +13,7 @@
 #include <vector>
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
diff --git a/chrome/browser/feed/android/feed_stream.cc b/chrome/browser/feed/android/feed_stream.cc
index ba59eb7..0895757 100644
--- a/chrome/browser/feed/android/feed_stream.cc
+++ b/chrome/browser/feed/android/feed_stream.cc
@@ -339,6 +339,13 @@
       (static_cast<StreamKind>(stream_kind)));
 }
 
+void FeedStream::ContentViewed(JNIEnv* env, uint64_t docid) {
+  if (!feed_stream_api_) {
+    return;
+  }
+  feed_stream_api_->RecordContentViewed(docid);
+}
+
 void FeedStream::ReportContentSliceVisibleTimeForGoodVisits(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/feed/android/feed_stream.h b/chrome/browser/feed/android/feed_stream.h
index 2b72b18..8584f58d 100644
--- a/chrome/browser/feed/android/feed_stream.h
+++ b/chrome/browser/feed/android/feed_stream.h
@@ -142,6 +142,8 @@
       const base::android::JavaParamRef<jobject>& obj,
       jint stream_kind);
 
+  void ContentViewed(JNIEnv* env, uint64_t docid);
+
   void ReportContentSliceVisibleTimeForGoodVisits(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
index f589dec..ff2222e 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
@@ -566,6 +566,12 @@
             FeedStreamJni.get().resetInfoCardStates(mNativeFeedStream, FeedStream.this, type);
         }
 
+        @Override
+        public void contentViewed(long docId) {
+            assert ThreadUtils.runningOnUiThread();
+            FeedStreamJni.get().contentViewed(mNativeFeedStream, docId);
+        }
+
         private @StreamType int feedIdentifierToType(@FeedIdentifier int fid) {
             switch (fid) {
                 case FeedIdentifier.MAIN_FEED:
@@ -1460,5 +1466,6 @@
                 long nativeFeedStream, FeedStream caller, @StreamType int feedToInvalidate);
         void reportContentSliceVisibleTimeForGoodVisits(
                 long nativeFeedStream, FeedStream caller, long elapsedMs);
+        void contentViewed(long nativeFeedStream, long docid);
     }
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 929de4d..20535419 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -227,6 +227,11 @@
     "expiry_milestone": 115
   },
   {
+    "name": "arc-aaudio-mmap-low-latency",
+    "owners": [ "pteerapong", "chromeos-audio@google.com" ],
+    "expiry_milestone": 125
+  },
+  {
     "name": "arc-arc-on-demand",
     "owners": [ "yuholong" ],
     "expiry_milestone": 130
@@ -1905,7 +1910,7 @@
   {
     "name": "enable-baseline-gm3-surface-colors",
     "owners": ["nemco", "skavuluru"],
-    "expiry_milestone": 115
+    "expiry_milestone": 120
   },
   {
     "name": "enable-benchmarking",
@@ -3332,6 +3337,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "enable-tab-strip-startup-refactoring",
+    "owners": [ "skavuluru", "nemco", "clank-large-form-factors@google.com" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "enable-tab-switcher-on-return",
     "owners": [ "memex-team@google.com" ],
     "expiry_milestone": 108
@@ -6247,7 +6257,7 @@
   {
     "name": "private-state-tokens",
     "owners": [ "svaldez", "aykutb@google.com" ],
-    "expiry_milestone": 105
+    "expiry_milestone": 120
   },
   {    "name": "productivity-launcher",
     "owners": [ "cros-system-ui-eng@google.com" ],
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index df44436..b99ceac4 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4250,6 +4250,13 @@
     "Enables fix for jank seen during fold to unfold transition on foldables "
     "with Android 12+";
 
+const char kTabStripStartupRefactoringName[] =
+    "Refactor for tablet tab strip startup.";
+const char kTabStripStartupRefactoringDescription[] =
+    "Enables refactor for tablet tab strip startup. This creates placeholder "
+    "tabs before the tab strip is initialized to prevent "
+    "jank (tabs seeming to quickly flicker / scroll).";
+
 const char kBaselineGM3SurfaceColorsName[] = "Baseline GM3 Surface Colors";
 const char kBaselineGM3SurfaceColorsDescription[] =
     "Updates baseline surface colors to match the GM3 formula.";
@@ -5121,6 +5128,11 @@
     "Enable AAudio MMAP support for ARCVM which provides low latency audio "
     "for supported apps.";
 
+const char kArcAAudioMMAPLowLatencyName[] =
+    "Enable ARCVM AAudio MMAP low latency";
+const char kArcAAudioMMAPLowLatencyDescription[] =
+    "When enabled, ARCVM AAudio MMAP will use low latency setting.";
+
 const char kArcEnableVirtioBlkForDataName[] =
     "Enable virtio-blk for ARCVM /data";
 const char kArcEnableVirtioBlkForDataDesc[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 39055fa0..d6736d1 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1791,6 +1791,9 @@
 extern const char kFoldableJankFixAndroidName[];
 extern const char kFoldableJankFixAndroidDescription[];
 
+extern const char kTabStripStartupRefactoringName[];
+extern const char kTabStripStartupRefactoringDescription[];
+
 extern const char kBaselineGM3SurfaceColorsName[];
 extern const char kBaselineGM3SurfaceColorsDescription[];
 
@@ -2935,6 +2938,9 @@
 extern const char kArcEnableAAudioMMAPName[];
 extern const char kArcEnableAAudioMMAPDescription[];
 
+extern const char kArcAAudioMMAPLowLatencyName[];
+extern const char kArcAAudioMMAPLowLatencyDescription[];
+
 extern const char kArcEnableVirtioBlkForDataName[];
 extern const char kArcEnableVirtioBlkForDataDesc[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 3d3cc05..dc288ac 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -311,6 +311,7 @@
     &kTabGroupsForTablets,
     &kDiscoverFeedMultiColumn,
     &kTabStripRedesign,
+    &kTabStripStartupRefactoring,
     &kTabGridLayoutAndroid,
     &kTabStateV1Optimizations,
     &kTabToGTSAnimation,
@@ -1018,6 +1019,10 @@
              "TabStripRedesign",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kTabStripStartupRefactoring,
+             "TabStripStartupRefactoring",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enabled, but often disabled in tests to reduce animation flakes and test
 // low-end device behavior where this animation is disabled.
 BASE_FEATURE(kTabToGTSAnimation,
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 4b13355..b784f821 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -173,6 +173,7 @@
 BASE_DECLARE_FEATURE(kTabStateV1Optimizations);
 BASE_DECLARE_FEATURE(kDiscoverFeedMultiColumn);
 BASE_DECLARE_FEATURE(kTabStripRedesign);
+BASE_DECLARE_FEATURE(kTabStripStartupRefactoring);
 BASE_DECLARE_FEATURE(kTabToGTSAnimation);
 BASE_DECLARE_FEATURE(kTestDefaultDisabled);
 BASE_DECLARE_FEATURE(kTestDefaultEnabled);
diff --git a/chrome/browser/google/google_brand_code_map_chromeos.cc b/chrome/browser/google/google_brand_code_map_chromeos.cc
index a653c5a..39107d51 100644
--- a/chrome/browser/google/google_brand_code_map_chromeos.cc
+++ b/chrome/browser/google/google_brand_code_map_chromeos.cc
@@ -183,6 +183,7 @@
                      {"GOKU", {"PRAG", "PQVF", "PIDI"}},
                      {"GRHN", {"XECM", "MGKM", "RXUC"}},
                      {"GSKT", {"MVLZ", "LUXS", "UIOA"}},
+                     {"GURF", {"LJBU", "NBCU", "SYMX"}},
                      {"GVLR", {"HCKU", "VUNU", "FIRF"}},
                      {"GWBB", {"RAZC", "LCUV", "TGLN"}},
                      {"GWDK", {"MQJZ", "WTMH", "ZOYJ"}},
@@ -251,6 +252,7 @@
                      {"JPUQ", {"OVKI", "AHZL", "YMJY"}},
                      {"JPZQ", {"CCBQ", "ABTW", "KFNE"}},
                      {"JQAO", {"OJYT", "ZDWK", "RQXZ"}},
+                     {"JQII", {"IMPG", "WLDA", "YISH"}},
                      {"JQUD", {"CUTW", "DLJE", "DOON"}},
                      {"JRJH", {"CPDL", "VCTT", "NBID"}},
                      {"JRVR", {"WGPS", "YETD", "KBWB"}},
@@ -598,6 +600,7 @@
                      {"ZIWS", {"GSAE", "JJUF", "ZPRA"}},
                      {"ZJLO", {"HLMS", "OHWG", "HMAL"}},
                      {"ZKJH", {"OBDQ", "OUAQ", "SPYY"}},
+                     {"ZKPX", {"UCDQ", "MJOD", "TSCT"}},
                      {"ZLBC", {"DJCJ", "HNGZ", "IRYZ"}},
                      {"ZLJE", {"NIZI", "ZWAH", "OAQL"}},
                      {"ZMHB", {"YRPB", "KPOF", "SBIB"}},
diff --git a/chrome/browser/hid/hid_status_icon_unittest.cc b/chrome/browser/hid/hid_status_icon_unittest.cc
index c9ecb79..00acc76 100644
--- a/chrome/browser/hid/hid_status_icon_unittest.cc
+++ b/chrome/browser/hid/hid_status_icon_unittest.cc
@@ -276,13 +276,13 @@
 
 TEST_F(HidStatusIconTest, ProfileUserName) {
   std::vector<HidSystemTrayIconTestBase::ProfileItem> profile_connection_counts;
-  std::vector<std::string> profile_names;
-  std::vector<std::string> new_profile_names;
-  std::vector<base::FilePath> profile_paths;
+  // std::get<1>(profiles[i]) is the old profile name.
+  // std::get<2>(profiles[i]) is the new profile name.
+  std::vector<std::tuple<Profile*, std::string, std::string>> profiles;
   for (size_t idx = 0; idx < 2; idx++) {
-    profile_names.push_back(base::StringPrintf("user%zu", idx));
-    new_profile_names.push_back(base::StringPrintf("user%zu-newname", idx));
-    auto* profile = CreateTestingProfile(profile_names.back());
+    std::string profile_name = base::StringPrintf("user%zu", idx);
+    std::string new_profile_name = base::StringPrintf("user%zu-newname", idx);
+    auto* profile = CreateTestingProfile(profile_name);
     auto extension = CreateExtensionWithName("Test Extension");
     AddExtensionToProfile(profile, extension.get());
     auto* connection_tracker =
@@ -291,7 +291,7 @@
     connection_tracker->IncrementConnectionCount(extension->origin());
     profile_connection_counts.push_back(
         {profile, {{extension->origin(), 1, extension->name()}}});
-    profile_paths.push_back(profile->GetPath());
+    profiles.emplace_back(profile, profile_name, new_profile_name);
   }
   CheckIcon(profile_connection_counts);
 
@@ -302,35 +302,50 @@
   const auto* status_icon = static_cast<MockStatusIcon*>(
       status_tray->GetStatusIconsForTest().back().get());
 
+  // Sort the |profiles| by the address of the profile
+  // pointer. This is necessary because the menu items are created by
+  // iterating through a structure of flat_map<Profile*, bool>.
+  base::ranges::sort(profiles);
+
   // Check the current profile names.
   {
     auto* menu_item = status_icon->menu_item();
-    CheckMenuItemLabel(menu_item, 3, base::UTF8ToUTF16(profile_names[0]));
-    CheckMenuItemLabel(menu_item, 7, base::UTF8ToUTF16(profile_names[1]));
+    CheckMenuItemLabel(
+        menu_item, 3,
+        base::UTF8ToUTF16(std::get<0>(profiles[0])->GetProfileUserName()));
+    CheckMenuItemLabel(
+        menu_item, 7,
+        base::UTF8ToUTF16(std::get<0>(profiles[1])->GetProfileUserName()));
   }
 
   // Change the first profile name.
   {
     profile_manager()
         ->profile_attributes_storage()
-        ->GetProfileAttributesWithPath(profile_paths[0])
-        ->SetLocalProfileName(base::UTF8ToUTF16(new_profile_names[0]),
+        ->GetProfileAttributesWithPath(std::get<0>(profiles[0])->GetPath())
+        ->SetLocalProfileName(base::UTF8ToUTF16(std::get<2>(profiles[0])),
                               /*is_default_name*/ false);
+
     auto* menu_item = status_icon->menu_item();
-    CheckMenuItemLabel(menu_item, 3, base::UTF8ToUTF16(new_profile_names[0]));
-    CheckMenuItemLabel(menu_item, 7, base::UTF8ToUTF16(profile_names[1]));
+    CheckMenuItemLabel(menu_item, 3,
+                       base::UTF8ToUTF16(std::get<2>(profiles[0])));
+    CheckMenuItemLabel(menu_item, 7,
+                       base::UTF8ToUTF16(std::get<1>(profiles[1])));
   }
 
   // Change the second profile name.
   {
     profile_manager()
         ->profile_attributes_storage()
-        ->GetProfileAttributesWithPath(profile_paths[1])
-        ->SetLocalProfileName(base::UTF8ToUTF16(new_profile_names[1]),
+        ->GetProfileAttributesWithPath(std::get<0>(profiles[1])->GetPath())
+        ->SetLocalProfileName(base::UTF8ToUTF16(std::get<2>(profiles[1])),
                               /*is_default_name*/ false);
+
     auto* menu_item = status_icon->menu_item();
-    CheckMenuItemLabel(menu_item, 3, base::UTF8ToUTF16(new_profile_names[0]));
-    CheckMenuItemLabel(menu_item, 7, base::UTF8ToUTF16(new_profile_names[1]));
+    CheckMenuItemLabel(menu_item, 3,
+                       base::UTF8ToUTF16(std::get<2>(profiles[0])));
+    CheckMenuItemLabel(menu_item, 7,
+                       base::UTF8ToUTF16(std::get<2>(profiles[1])));
   }
 }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/history/history_tab_helper.h b/chrome/browser/history/history_tab_helper.h
index e8bc595..859f9067 100644
--- a/chrome/browser/history/history_tab_helper.h
+++ b/chrome/browser/history/history_tab_helper.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_HISTORY_HISTORY_TAB_HELPER_H_
 #define CHROME_BROWSER_HISTORY_HISTORY_TAB_HELPER_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
diff --git a/chrome/browser/history_clusters/history_clusters_service_factory.cc b/chrome/browser/history_clusters/history_clusters_service_factory.cc
index 34ae927..5afacf9 100644
--- a/chrome/browser/history_clusters/history_clusters_service_factory.cc
+++ b/chrome/browser/history_clusters/history_clusters_service_factory.cc
@@ -20,6 +20,32 @@
 #include "components/site_engagement/content/site_engagement_service.h"
 #include "content/public/browser/storage_partition.h"
 
+namespace {
+
+std::unique_ptr<KeyedService> BuildService(content::BrowserContext* context) {
+  auto* profile = Profile::FromBrowserContext(context);
+  auto* history_service = HistoryServiceFactory::GetForProfile(
+      profile, ServiceAccessType::EXPLICIT_ACCESS);
+
+  // The clusters service can't function without a HistoryService. This happens
+  // in some unit tests.
+  if (!history_service) {
+    return nullptr;
+  }
+
+  auto url_loader_factory = context->GetDefaultStoragePartition()
+                                ->GetURLLoaderFactoryForBrowserProcess();
+  return std::make_unique<history_clusters::HistoryClustersService>(
+      g_browser_process->GetApplicationLocale(), history_service,
+      PageContentAnnotationsServiceFactory::GetForProfile(profile),
+      url_loader_factory, site_engagement::SiteEngagementService::Get(profile),
+      TemplateURLServiceFactory::GetForProfile(profile),
+      OptimizationGuideKeyedServiceFactory::GetForProfile(profile),
+      profile->GetPrefs());
+}
+
+}  // namespace
+
 // static
 history_clusters::HistoryClustersService*
 HistoryClustersServiceFactory::GetForBrowserContext(
@@ -53,26 +79,15 @@
 
 HistoryClustersServiceFactory::~HistoryClustersServiceFactory() = default;
 
+// static
+BrowserContextKeyedServiceFactory::TestingFactory
+HistoryClustersServiceFactory::GetDefaultFactory() {
+  return base::BindRepeating(&BuildService);
+}
+
 KeyedService* HistoryClustersServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  auto* profile = Profile::FromBrowserContext(context);
-  auto* history_service = HistoryServiceFactory::GetForProfile(
-      profile, ServiceAccessType::EXPLICIT_ACCESS);
-
-  // The clusters service can't function without a HistoryService. This happens
-  // in some unit tests.
-  if (!history_service)
-    return nullptr;
-
-  auto url_loader_factory = context->GetDefaultStoragePartition()
-                                ->GetURLLoaderFactoryForBrowserProcess();
-  return new history_clusters::HistoryClustersService(
-      g_browser_process->GetApplicationLocale(), history_service,
-      PageContentAnnotationsServiceFactory::GetForProfile(profile),
-      url_loader_factory, site_engagement::SiteEngagementService::Get(profile),
-      TemplateURLServiceFactory::GetForProfile(profile),
-      OptimizationGuideKeyedServiceFactory::GetForProfile(profile),
-      profile->GetPrefs());
+  return BuildService(context).release();
 }
 
 // static
diff --git a/chrome/browser/history_clusters/history_clusters_service_factory.h b/chrome/browser/history_clusters/history_clusters_service_factory.h
index 98cb2ed..e8cb7dc 100644
--- a/chrome/browser/history_clusters/history_clusters_service_factory.h
+++ b/chrome/browser/history_clusters/history_clusters_service_factory.h
@@ -25,6 +25,9 @@
 
   static void EnsureFactoryBuilt();
 
+  // Returns the default factory, useful in tests where it's null by default.
+  static TestingFactory GetDefaultFactory();
+
  private:
   friend base::NoDestructor<HistoryClustersServiceFactory>;
 
diff --git a/chrome/browser/mac/keystone_glue.mm b/chrome/browser/mac/keystone_glue.mm
index d7371d6..7eda97a 100644
--- a/chrome/browser/mac/keystone_glue.mm
+++ b/chrome/browser/mac/keystone_glue.mm
@@ -11,12 +11,12 @@
 #include <utility>
 #include <vector>
 
+#include "base/apple/bridging.h"
 #include "base/file_version_info.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/mac/authorization_util.h"
-#include "base/mac/bridging.h"
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
@@ -818,7 +818,7 @@
 // tests can pick it up.
 + (BOOL)isValidSystemKeystone:(NSDictionary*)systemKeystonePlistContents
             comparedToBundled:(NSDictionary*)bundledKeystonePlistContents {
-  NSString* versionKey = base::mac::CFToNSPtrCast(kCFBundleVersionKey);
+  NSString* versionKey = base::apple::CFToNSPtrCast(kCFBundleVersionKey);
 
   // If the bundled version is missing or broken, this question is irrelevant.
   NSString* bundledKeystoneVersionString =
@@ -917,7 +917,7 @@
       l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
   base::mac::ScopedAuthorizationRef authorization =
       base::mac::AuthorizationCreateToRunAsRoot(
-          base::mac::NSToCFPtrCast(prompt));
+          base::apple::NSToCFPtrCast(prompt));
   if (!authorization) {
     return;
   }
diff --git a/chrome/browser/media/cdm_pref_service_helper.cc b/chrome/browser/media/cdm_pref_service_helper.cc
index 0a7f5e3..46c0f42c 100644
--- a/chrome/browser/media/cdm_pref_service_helper.cc
+++ b/chrome/browser/media/cdm_pref_service_helper.cc
@@ -45,22 +45,22 @@
 //     },
 //     more origin_string map...
 // }
-base::Value ToDictValue(const CdmPrefData& pref_data) {
-  base::Value dict(base::Value::Type::DICT);
-
+base::Value::Dict ToDictValue(const CdmPrefData& pref_data) {
   // Origin ID
-  dict.SetKey(kOriginId, base::UnguessableTokenToValue(pref_data.origin_id()));
-  dict.SetKey(kOriginIdCreationTime,
-              base::TimeToValue(pref_data.origin_id_creation_time()));
+  auto dict =
+      base::Value::Dict()
+          .Set(kOriginId, base::UnguessableTokenToValue(pref_data.origin_id()))
+          .Set(kOriginIdCreationTime,
+               base::TimeToValue(pref_data.origin_id_creation_time()));
 
   // Optional Client Token
   const absl::optional<std::vector<uint8_t>> client_token =
       pref_data.client_token();
   if (client_token.has_value() && !client_token->empty()) {
     std::string encoded_client_token = base::Base64Encode(client_token.value());
-    dict.SetStringKey(kClientToken, encoded_client_token);
-    dict.SetKey(kClientTokenCreationTime,
-                base::TimeToValue(pref_data.client_token_creation_time()));
+    dict.Set(kClientToken, encoded_client_token);
+    dict.Set(kClientTokenCreationTime,
+             base::TimeToValue(pref_data.client_token_creation_time()));
   }
 
   return dict;
diff --git a/chrome/browser/media/encrypted_media_browsertest.cc b/chrome/browser/media/encrypted_media_browsertest.cc
index 72bb7225..2302c38 100644
--- a/chrome/browser/media/encrypted_media_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_browsertest.cc
@@ -39,6 +39,7 @@
 #include "testing/gtest/include/gtest/gtest-spi.h"
 #include "third_party/widevine/cdm/buildflags.h"
 #include "third_party/widevine/cdm/widevine_cdm_common.h"
+#include "ui/gl/gl_switches.h"
 
 #if BUILDFLAG(IS_WIN)
 #include "base/win/windows_version.h"
@@ -323,6 +324,19 @@
       // capabilities can be checked.
       command_line->AppendSwitchASCII(
           switches::kOverrideHardwareSecureCodecsForTesting, "avc1,mp4a");
+
+      // To enable MediaFoundation playback, tests should run on a hardware GPU
+      // other than use a software OpenGL implementation. This can be configured
+      // via `switches::kUseGpuInTests` or `--use-gpu-in-tests`.
+      if (command_line->HasSwitch(switches::kUseGpuInTests)) {
+        // TODO(crbug.com/1421444): Investigate why the video playback doesn't
+        // work with `switches::kDisableGpu` and remove this line if possible.
+        // For now, `switches::kDisableGpu` should not be set. Otherwise,
+        // the video playback will not work with software rendering. Note that
+        // this switch is appended to browser_tests.exe by force as a workaround
+        // of http://crbug.com/687387.
+        command_line->RemoveSwitch(switches::kDisableGpu);
+      }
     }
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -1068,24 +1082,42 @@
   }
 
   bool IsMediaFoundationEncryptedPlaybackSupported() {
-    static bool is_mediafoundation_encrypted_playback_supported =
+    bool is_mediafoundation_encrypted_playback_supported =
         media::SupportMediaFoundationEncryptedPlayback();
+    bool use_gpu_in_tests = base::CommandLine::ForCurrentProcess()->HasSwitch(
+        switches::kUseGpuInTests);
+    bool disable_gpu = base::CommandLine::ForCurrentProcess()->HasSwitch(
+        switches::kDisableGpu);
+    bool is_playback_supported =
+        is_mediafoundation_encrypted_playback_supported && use_gpu_in_tests &&
+        !disable_gpu;
     DLOG(INFO) << "is_mediafoundation_encrypted_playback_supported="
-               << is_mediafoundation_encrypted_playback_supported;
+               << is_mediafoundation_encrypted_playback_supported
+               << ", use_gpu_in_tests=" << use_gpu_in_tests
+               << ", disable_gpu=" << disable_gpu;
 
     // Run test only if the test machine supports MediaFoundation playback.
-    // Otherwise, NotSupportedError is expected.
-    if (!is_mediafoundation_encrypted_playback_supported) {
-      auto os_version = static_cast<int>(base::win::GetVersion());
+    // Otherwise, NotSupportedError or the failure to create D3D11 device is
+    // expected.
+    if (!is_playback_supported) {
       DLOG(WARNING)
           << "Test method "
           << ::testing::UnitTest::GetInstance()->current_test_info()->name()
-          << " is inconclusive since MediaFoundation "
-             "playback is not supported. "
-          << "os_version=" << os_version;
+          << " is inconclusive since MediaFoundation playback is not "
+             "supported.";
+
+      if (!is_mediafoundation_encrypted_playback_supported) {
+        auto os_version = static_cast<int>(base::win::GetVersion());
+        DLOG(WARNING) << "os_version=" << os_version;
+      }
+
+      if (!use_gpu_in_tests) {
+        DLOG(WARNING) << "MediaFoundation playback will not work without a "
+                         "hardware GPU. Use `--use-gpu-in-tests` flag.";
+      }
     }
 
-    return is_mediafoundation_encrypted_playback_supported;
+    return is_playback_supported;
   }
 };
 
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn
index b605b41..51b2f832 100644
--- a/chrome/browser/media/router/BUILD.gn
+++ b/chrome/browser/media/router/BUILD.gn
@@ -99,7 +99,9 @@
 
       "discovery:discovery",
       "//chrome/browser:browser_process",
+      "//chrome/browser/media/router/discovery/access_code:access_code_cast_feature",
       "//chrome/browser/profiles:profile",
+      "//components/access_code_cast/common:metrics",
       "//components/embedder_support:browser_util",
       "//components/mirroring/mojom:service",
       "//components/ukm/content:content",
diff --git a/chrome/browser/media/router/discovery/access_code/BUILD.gn b/chrome/browser/media/router/discovery/access_code/BUILD.gn
index 6acba19..44d5b67 100644
--- a/chrome/browser/media/router/discovery/access_code/BUILD.gn
+++ b/chrome/browser/media/router/discovery/access_code/BUILD.gn
@@ -61,7 +61,7 @@
       "//chrome/browser/profiles:profile",
       "//chrome/browser/signin:identity_manager_provider",
       "//chrome/browser/ui/webui/access_code_cast:mojo_bindings",
-      "//components/access_code_cast/common",
+      "//components/access_code_cast/common:metrics",
       "//components/endpoint_fetcher:endpoint_fetcher",
       "//components/keyed_service/content:content",
       "//components/leveldb_proto:leveldb_proto",
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_discovery_interface.h b/chrome/browser/media/router/discovery/access_code/access_code_cast_discovery_interface.h
index 7fe58f3..fad4e56e 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_discovery_interface.h
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_discovery_interface.h
@@ -9,6 +9,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/media/router/discovery/access_code/discovery_resources.pb.h"
diff --git a/chrome/browser/media/router/mojo/media_router_debugger_impl.h b/chrome/browser/media/router/mojo/media_router_debugger_impl.h
index 28dfa10..017c152f 100644
--- a/chrome/browser/media/router/mojo/media_router_debugger_impl.h
+++ b/chrome/browser/media/router/mojo/media_router_debugger_impl.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_MEDIA_ROUTER_MOJO_MEDIA_ROUTER_DEBUGGER_IMPL_H_
 #define CHROME_BROWSER_MEDIA_ROUTER_MOJO_MEDIA_ROUTER_DEBUGGER_IMPL_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "base/values.h"
diff --git a/chrome/browser/media/router/providers/cast/cast_media_route_provider.h b/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
index 5fb4c37..9569137 100644
--- a/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
+++ b/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/containers/flat_map.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/media/router/providers/cast/cast_app_discovery_service.h"
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.cc b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
index 6a48011..8127011 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.cc
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
@@ -25,10 +25,13 @@
 #include "base/values.h"
 #include "chrome/browser/media/cast_mirroring_service_host_factory.h"
 #include "chrome/browser/media/router/data_decoder_util.h"
+#include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/media/router/providers/cast/cast_activity_manager.h"
 #include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/access_code_cast/common/access_code_cast_metrics.h"
 #include "components/media_router/browser/media_router_debugger.h"
 #include "components/media_router/browser/mirroring_to_flinging_switcher.h"
 #include "components/media_router/common/discovery/media_sink_internal.h"
@@ -181,6 +184,26 @@
     return;
   }
 
+  // Record mirroring pause metrics.
+  if (mirroring_pause_timestamp_) {  // The session is ending while paused.
+    AccessCodeCastMetrics::RecordMirroringPauseDuration(
+        base::Time::Now() - mirroring_pause_timestamp_.value());
+  }
+  // We can only get a profile on the UI thread, so we must post a task to check
+  // if we should log certain metrics.
+  content::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](int pause_count) {
+            // Don't record pause count if the cast session cannot be paused.
+            if (pause_count > 0 ||
+                media_router::IsAccessCodeCastFreezeUiEnabled(
+                    ProfileManager::GetLastUsedProfile())) {
+              AccessCodeCastMetrics::RecordMirroringPauseCount(pause_count);
+            }
+          },
+          mirroring_pause_count_));
+
   auto cast_duration = base::Time::Now() - *did_start_mirroring_timestamp_;
   base::UmaHistogramLongTimes(kHistogramSessionLength, cast_duration);
 
@@ -359,6 +382,7 @@
   // The source changed, which means that a new capturer was created that is
   // now sending frames. Ensure the state is now PLAYING.
   media_status_->play_state = mojom::MediaStatus::PlayState::PLAYING;
+  OnMirroringResumed();
   NotifyMediaStatusObserver();
 
   content::GetUIThreadTaskRunner({})->PostTask(
@@ -371,6 +395,7 @@
   // Transitions to/from remoting restart the capturer. Set the state to
   // playing.
   media_status_->play_state = mojom::MediaStatus::PlayState::PLAYING;
+  OnMirroringResumed();
   NotifyMediaStatusObserver();
 }
 
@@ -729,6 +754,11 @@
 
 void MirroringActivity::SetPlayState(mojom::MediaStatus::PlayState play_state) {
   media_status_->play_state = play_state;
+  if (play_state == mojom::MediaStatus::PlayState::PLAYING) {
+    OnMirroringResumed();
+  } else if (play_state == mojom::MediaStatus::PlayState::PAUSED) {
+    OnMirroringPaused();
+  }
   NotifyMediaStatusObserver();
 }
 
@@ -738,4 +768,22 @@
   }
 }
 
+void MirroringActivity::OnMirroringPaused() {
+  // Do nothing if we are already paused.
+  if (mirroring_pause_timestamp_) {
+    return;
+  }
+  mirroring_pause_timestamp_ = base::Time::Now();
+  mirroring_pause_count_++;
+}
+
+void MirroringActivity::OnMirroringResumed() {
+  if (!mirroring_pause_timestamp_) {
+    return;
+  }
+  AccessCodeCastMetrics::RecordMirroringPauseDuration(
+      base::Time::Now() - mirroring_pause_timestamp_.value());
+  mirroring_pause_timestamp_.reset();
+}
+
 }  // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.h b/chrome/browser/media/router/providers/cast/mirroring_activity.h
index b5ccf561..5ac4033 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.h
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.h
@@ -144,6 +144,10 @@
 
   void NotifyMediaStatusObserver();
 
+  // Invoked when mirroring is paused / resumed, for metrics.
+  void OnMirroringPaused();
+  void OnMirroringResumed();
+
   // Scrubs AES related data in messages with type "OFFER".
   static std::string GetScrubbedLogMessage(const base::Value::Dict& message);
 
@@ -203,7 +207,10 @@
   // the session.
   mojo::Remote<mojom::MediaStatusObserver> media_status_observer_;
 
+  // Info for mirroring state transitions like pause / resume.
   mojom::MediaStatusPtr media_status_;
+  int mirroring_pause_count_ = 0;
+  absl::optional<base::Time> mirroring_pause_timestamp_;
 
   // Set before and after a mirroring session is established, for metrics.
   absl::optional<base::Time> will_start_mirroring_timestamp_;
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc b/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
index 8ee582d0..7b60b90 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
@@ -618,6 +618,47 @@
   testing::Mock::VerifyAndClearExpectations(&media_status_observer);
 }
 
+TEST_F(MirroringActivityTest, PauseAndPlay) {
+  base::HistogramTester uma_recorder;
+  EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId));
+  MediaSource source = MediaSource::ForTab(kTabId);
+  MakeActivity(source, kFrameTreeNodeId,
+               CastDiscoveryType::kAccessCodeManualEntry);
+  auto cb = [&](base::OnceClosure callback) { std::move(callback).Run(); };
+  EXPECT_CALL(*mirroring_service_, Pause(_)).WillOnce(testing::Invoke(cb));
+  EXPECT_CALL(*mirroring_service_, Resume(_)).WillOnce(testing::Invoke(cb));
+
+  activity_->DidStart();
+  activity_->Pause();
+  base::RunLoop().RunUntilIdle();
+  activity_->Play();
+  base::RunLoop().RunUntilIdle();
+  activity_.reset();
+  base::RunLoop().RunUntilIdle();
+
+  uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeCount", 1);
+  uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeDuration", 1);
+}
+
+TEST_F(MirroringActivityTest, PauseAndReset) {
+  base::HistogramTester uma_recorder;
+  EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId));
+  MediaSource source = MediaSource::ForTab(kTabId);
+  MakeActivity(source, kFrameTreeNodeId,
+               CastDiscoveryType::kAccessCodeManualEntry);
+  auto cb = [&](base::OnceClosure callback) { std::move(callback).Run(); };
+  EXPECT_CALL(*mirroring_service_, Pause(_)).WillOnce(testing::Invoke(cb));
+
+  activity_->DidStart();
+  activity_->Pause();
+  base::RunLoop().RunUntilIdle();
+  activity_.reset();
+  base::RunLoop().RunUntilIdle();
+
+  uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeCount", 1);
+  uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeDuration", 1);
+}
+
 TEST_F(MirroringActivityTest, OnRemotingStateChanged) {
   MakeActivity();
   mojo::PendingRemote<mojom::MediaStatusObserver> observer_pending_remote;
diff --git a/chrome/browser/memory/memory_kills_monitor.h b/chrome/browser/memory/memory_kills_monitor.h
index c344d72..90e6723 100644
--- a/chrome/browser/memory/memory_kills_monitor.h
+++ b/chrome/browser/memory/memory_kills_monitor.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/synchronization/atomic_flag.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/login/login_state/login_state.h"
diff --git a/chrome/browser/memory/oom_kills_monitor.h b/chrome/browser/memory/oom_kills_monitor.h
index 1c9e528..615aaf28 100644
--- a/chrome/browser/memory/oom_kills_monitor.h
+++ b/chrome/browser/memory/oom_kills_monitor.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_MEMORY_OOM_KILLS_MONITOR_H_
 #define CHROME_BROWSER_MEMORY_OOM_KILLS_MONITOR_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/no_destructor.h"
 #include "base/synchronization/atomic_flag.h"
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos.cc b/chrome/browser/metrics/per_user_state_manager_chromeos.cc
index b7f85e7..9dec49c 100644
--- a/chrome/browser/metrics/per_user_state_manager_chromeos.cc
+++ b/chrome/browser/metrics/per_user_state_manager_chromeos.cc
@@ -178,8 +178,9 @@
 absl::optional<bool>
 PerUserStateManagerChromeOS::GetCurrentUserReportingConsentIfApplicable()
     const {
-  if (state_ != State::USER_LOG_STORE_HANDLED)
+  if (state_ != State::USER_LOG_STORE_HANDLED || !IsDeviceStatusKnown()) {
     return absl::nullopt;
+  }
 
   // Guest sessions with no device owner should use the guest's metrics
   // consent set during guest OOBE flow with no device owner.
@@ -227,6 +228,12 @@
 bool PerUserStateManagerChromeOS::ShouldUseUserLogStore() const {
   DCHECK(state_ > State::CONSTRUCTED);
 
+  if (!IsDeviceStatusKnown()) {
+    LOG(ERROR) << "Device ownership status unknown. Cannot accurately "
+                  "determine whether per-user should be used or not.";
+    return false;
+  }
+
   if (user_manager_->IsCurrentUserCryptohomeDataEphemeral()) {
     // Sessions using ephemeral cryptohome should hold the logs if owner has
     // disabled metrics reporting. This way the recorded logs are deleted when
@@ -254,6 +261,15 @@
   if (user->GetAccountId() == user_manager_->GetOwnerAccountId())
     return false;
 
+  // If the device status is not known yet, then the user should not be allowed
+  // to opt-in until the status is known.
+  DCHECK(IsDeviceStatusKnown());
+  if (!IsDeviceStatusKnown()) {
+    LOG(ERROR) << "Device ownership status unknown. Cannot accurately "
+                  "determine whether per-user should be used or not.";
+    return false;
+  }
+
   auto user_type = user->GetType();
 
   // Guest sessions for non-owned devices should be allowed to modify metrics
@@ -325,12 +341,7 @@
 }
 
 bool PerUserStateManagerChromeOS::GetDeviceMetricsConsent() const {
-  DCHECK_NE(ash::DeviceSettingsService::Get()->GetOwnershipStatus(),
-            ash::DeviceSettingsService::OWNERSHIP_UNKNOWN);
-
-  return ash::DeviceSettingsService::Get()->GetOwnershipStatus() ==
-             ash::DeviceSettingsService::OWNERSHIP_TAKEN &&
-         ash::StatsReportingController::Get()->IsEnabled();
+  return ash::StatsReportingController::Get()->IsEnabled();
 }
 
 bool PerUserStateManagerChromeOS::HasUserLogStore() const {
@@ -338,10 +349,17 @@
 }
 
 bool PerUserStateManagerChromeOS::IsDeviceOwned() const {
+  DCHECK(IsDeviceStatusKnown());
+
   return ash::DeviceSettingsService::Get()->GetOwnershipStatus() ==
          ash::DeviceSettingsService::OwnershipStatus::OWNERSHIP_TAKEN;
 }
 
+bool PerUserStateManagerChromeOS::IsDeviceStatusKnown() const {
+  return ash::DeviceSettingsService::Get()->GetOwnershipStatus() !=
+         ash::DeviceSettingsService::OwnershipStatus::OWNERSHIP_UNKNOWN;
+}
+
 void PerUserStateManagerChromeOS::ActiveUserChanged(user_manager::User* user) {
   // Logged-in user is already detected. Do nothing since multi-user is
   // deprecated and since the first user is the primary user.
@@ -407,6 +425,8 @@
   // If a guest session is about to be started, the metrics reporting will
   // normally inherit from the device owner's setting. If there is no owner,
   // then the guest will set metrics reporting during ToS.
+  //
+  // Note that device status is guaranteed to be known.
   if (is_guest && !IsDeviceOwned()) {
     SetReportingState(
         local_state_->GetBoolean(ash::prefs::kOobeGuestMetricsEnabled));
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos.h b/chrome/browser/metrics/per_user_state_manager_chromeos.h
index 803ec07..415d099 100644
--- a/chrome/browser/metrics/per_user_state_manager_chromeos.h
+++ b/chrome/browser/metrics/per_user_state_manager_chromeos.h
@@ -92,8 +92,7 @@
   // has opted-into metrics collection during the session and False means that
   // the user has opted-out.
   //
-  // Note: Use this function over GetUserConsentIfApplicable() to retrieve user
-  // metrics reporting status.
+  // NOTE: If ownership status is not known, this will return absl::nullopt.
   absl::optional<bool> GetCurrentUserReportingConsentIfApplicable() const;
 
   // Sets the metric consent for the current logged in user. If no user is
@@ -156,8 +155,7 @@
   // Returns true if the reporting policy is managed.
   virtual bool IsReportingPolicyManaged() const;
 
-  // Returns the device metrics consent. If ownership has not been taken, will
-  // return false.
+  // Returns the device metrics consent.
   virtual bool GetDeviceMetricsConsent() const;
 
   // Returns true if user log store has been set to be used to persist metric
@@ -166,11 +164,17 @@
 
   // Returns true if the device is owned either by a policy or a local owner.
   //
+  // Does not guarantee that the ownership status is known and will return false
+  // if the status is unknown.
+  //
   // See //chrome/browser/ash/settings/device_settings_service.h for more
   // details as to when a device is considered owned and how a device becomes
   // owned.
   virtual bool IsDeviceOwned() const;
 
+  // Returns true if the device status is known.
+  virtual bool IsDeviceStatusKnown() const;
+
   // These methods are protected to avoid dependency on DeviceSettingsService
   // during testing.
 
@@ -180,6 +184,8 @@
 
   // Loads appropriate prefs from |current_user_| and creates new log storage
   // using profile prefs.
+  //
+  // Will only be called when OwnershipStatus is known.
   void InitializeProfileMetricsState(
       ash::DeviceSettingsService::OwnershipStatus status);
 
@@ -193,7 +199,9 @@
     // immediately created.
     USER_LOGIN = 1,
 
-    // User profile has been created and ready to use.
+    // User profile has been created and ready to use. Note that if ownership
+    // status is unknown, user log store will not be created until the ownership
+    // status is known.
     USER_PROFILE_READY = 2,
 
     // User log store has been initialized, if applicable. Per-user consent
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc b/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc
index 2aeb2da..d5d9857 100644
--- a/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc
+++ b/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc
@@ -8,7 +8,6 @@
 #include "base/run_loop.h"
 #include "chrome/browser/ash/login/login_manager_test.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/guest_session_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/scoped_policy_update.h"
@@ -18,6 +17,7 @@
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/ash/settings/stats_reporting_controller.h"
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc b/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc
index 013b9b97..1cbcdc92 100644
--- a/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc
+++ b/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc
@@ -56,6 +56,10 @@
     is_device_owned_ = is_device_owned;
   }
 
+  void SetIsDeviceStatusKnown(bool is_device_status_known) {
+    is_device_status_known_ = is_device_status_known;
+  }
+
   bool is_log_store_set() const { return is_log_store_set_; }
   bool is_client_id_reset() const { return is_client_id_reset_; }
 
@@ -74,10 +78,14 @@
 
   bool IsDeviceOwned() const override { return is_device_owned_; }
 
+  bool IsDeviceStatusKnown() const override { return is_device_status_known_; }
+
   void WaitForOwnershipStatus() override {
-    InitializeProfileMetricsState(
-        is_device_owned_ ? ash::DeviceSettingsService::OWNERSHIP_TAKEN
-                         : ash::DeviceSettingsService::OWNERSHIP_NONE);
+    if (IsDeviceStatusKnown()) {
+      InitializeProfileMetricsState(
+          is_device_owned_ ? ash::DeviceSettingsService::OWNERSHIP_TAKEN
+                           : ash::DeviceSettingsService::OWNERSHIP_NONE);
+    }
   }
 
  private:
@@ -86,6 +94,7 @@
   bool is_managed_ = false;
   bool device_metrics_consent_ = true;
   bool is_device_owned_ = true;
+  bool is_device_status_known_ = true;
 };
 
 }  // namespace
@@ -485,4 +494,28 @@
   test_user2_profile.reset();
 }
 
+TEST_F(PerUserStateManagerChromeOSTest,
+       PerUserDisabledWhenOwnershipStatusUnknown) {
+  auto* test_user =
+      RegisterUser(AccountId::FromUserEmailGaiaId("test1@example.com", "1"));
+  InitializeProfileState(/*user_id=*/"", /*metrics_consent=*/true,
+                         /*has_consented_to_metrics=*/true);
+
+  // Ownership status of device is unknown.
+  GetPerUserStateManager()->SetIsDeviceStatusKnown(false);
+
+  // Simulate user login.
+  LoginRegularUser(test_user);
+
+  // User log store is created async. Ensure that the log store loading
+  // finishes.
+  RunUntilIdle();
+
+  // Per-user should not run if ownership status is unknown.
+  EXPECT_FALSE(GetPerUserStateManager()
+                   ->GetCurrentUserReportingConsentIfApplicable()
+                   .has_value());
+  EXPECT_FALSE(GetPerUserStateManager()->is_log_store_set());
+}
+
 }  // namespace metrics
diff --git a/chrome/browser/metrics/structured/BUILD.gn b/chrome/browser/metrics/structured/BUILD.gn
index f909c5a..3439516 100644
--- a/chrome/browser/metrics/structured/BUILD.gn
+++ b/chrome/browser/metrics/structured/BUILD.gn
@@ -7,50 +7,13 @@
 
 assert(is_chromeos)
 
-static_library("structured") {
+source_set("features") {
   sources = [
-    "chrome_structured_metrics_recorder.cc",
-    "chrome_structured_metrics_recorder.h",
-    "cros_events_processor.cc",
-    "cros_events_processor.h",
     "event_logging_features.cc",
     "event_logging_features.h",
   ]
 
-  deps = [
-    "//base",
-    "//build:chromeos_buildflags",
-    "//chromeos/crosapi/mojom",
-    "//components/metrics/structured",
-    "//components/metrics/structured:structured_events",
-    "//components/prefs",
-  ]
-
-  if (is_chromeos_ash) {
-    sources += [
-      "ash_structured_metrics_recorder.cc",
-      "ash_structured_metrics_recorder.h",
-      "structured_metrics_key_events_observer.cc",
-      "structured_metrics_key_events_observer.h",
-    ]
-
-    deps += [
-      "//chrome/browser:browser_process",
-      "//chrome/browser/ash/crosapi",
-      "//chromeos/ash/components/login/session:session",
-      "//chromeos/dbus/power:power",
-      "//components/user_manager:user_manager",
-    ]
-  }
-
-  if (is_chromeos_lacros) {
-    sources += [
-      "lacros_structured_metrics_recorder.cc",
-      "lacros_structured_metrics_recorder.h",
-    ]
-
-    deps += [ "//chromeos/lacros" ]
-  }
+  deps = [ "//base" ]
 }
 
 static_library("test_support") {
diff --git a/chrome/browser/metrics/structured/chrome_structured_metrics_recorder.cc b/chrome/browser/metrics/structured/chrome_structured_metrics_recorder.cc
index 9ab0ae6..5586605 100644
--- a/chrome/browser/metrics/structured/chrome_structured_metrics_recorder.cc
+++ b/chrome/browser/metrics/structured/chrome_structured_metrics_recorder.cc
@@ -8,7 +8,6 @@
 
 #include "base/no_destructor.h"
 #include "base/task/sequenced_task_runner.h"
-#include "chrome/browser/metrics/structured/cros_events_processor.h"
 #include "components/metrics/structured/histogram_util.h"
 #include "components/metrics/structured/recorder.h"
 #include "components/metrics/structured/structured_metrics_features.h"
@@ -16,6 +15,8 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/browser_process.h"  // nogncheck
 #include "chrome/browser/metrics/structured/ash_structured_metrics_recorder.h"  // nogncheck
+#include "chrome/browser/metrics/structured/cros_events_processor.h"  // nogncheck
+#include "chrome/browser/metrics/structured/metadata_processor_ash.h"  // nogncheck
 #elif BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "base/task/current_thread.h"
 #include "chrome/browser/metrics/structured/lacros_structured_metrics_recorder.h"  // nogncheck
@@ -80,6 +81,9 @@
             cros_event::kResetCounterPath));
   }
 
+  Recorder::GetInstance()->AddEventsProcessor(
+      std::make_unique<MetadataProcessorAsh>());
+
   LogInitializationInStructuredMetrics(StructuredMetricsPlatform::kAshChrome);
 
 #elif BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/metrics/structured/cros_events_processor.cc b/chrome/browser/metrics/structured/cros_events_processor.cc
index 285dc7c9..5b82148 100644
--- a/chrome/browser/metrics/structured/cros_events_processor.cc
+++ b/chrome/browser/metrics/structured/cros_events_processor.cc
@@ -37,4 +37,9 @@
       Event::EventSequenceMetadata(current_reset_counter_));
 }
 
+void CrOSEventsProcessor::OnProvideIndependentMetrics(
+    ChromeUserMetricsExtension* uma_proto) {
+  // no-op.
+}
+
 }  // namespace metrics::structured::cros_event
diff --git a/chrome/browser/metrics/structured/cros_events_processor.h b/chrome/browser/metrics/structured/cros_events_processor.h
index 7bda2c5..afebe45 100644
--- a/chrome/browser/metrics/structured/cros_events_processor.h
+++ b/chrome/browser/metrics/structured/cros_events_processor.h
@@ -25,6 +25,8 @@
   // EventsProcessorInterface:
   bool ShouldProcessOnEventRecord(const Event& event) override;
   void OnEventsRecord(Event* event) override;
+  void OnProvideIndependentMetrics(
+      ChromeUserMetricsExtension* uma_proto) override;
 
  private:
   // The current reset counter as determined by platform2.
diff --git a/chrome/browser/metrics/structured/metadata_processor_ash.cc b/chrome/browser/metrics/structured/metadata_processor_ash.cc
new file mode 100644
index 0000000..dca2e09
--- /dev/null
+++ b/chrome/browser/metrics/structured/metadata_processor_ash.cc
@@ -0,0 +1,28 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/structured/metadata_processor_ash.h"
+#include "chrome/browser/policy/management_utils.h"
+
+namespace metrics::structured {
+
+bool MetadataProcessorAsh::ShouldProcessOnEventRecord(const Event& event) {
+  return true;
+}
+
+void MetadataProcessorAsh::OnEventsRecord(Event* event) {
+  // no-op.
+}
+
+void MetadataProcessorAsh::OnProvideIndependentMetrics(
+    ChromeUserMetricsExtension* uma_proto) {
+  auto* structured_metrics = uma_proto->mutable_structured_data();
+  structured_metrics->set_is_device_enrolled(IsDeviceEnrolled());
+}
+
+bool MetadataProcessorAsh::IsDeviceEnrolled() {
+  return policy::IsDeviceEnterpriseManaged();
+}
+
+}  // namespace metrics::structured
diff --git a/chrome/browser/metrics/structured/metadata_processor_ash.h b/chrome/browser/metrics/structured/metadata_processor_ash.h
new file mode 100644
index 0000000..0f45778
--- /dev/null
+++ b/chrome/browser/metrics/structured/metadata_processor_ash.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_METRICS_STRUCTURED_METADATA_PROCESSOR_ASH_H_
+#define CHROME_BROWSER_METRICS_STRUCTURED_METADATA_PROCESSOR_ASH_H_
+
+#include "components/metrics/structured/events_processor_interface.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics::structured {
+namespace {
+
+using ::metrics::ChromeUserMetricsExtension;
+
+}
+
+// Retrieves metadata for Ash Chrome and attaches it to the Structured metrics
+// payload.
+class MetadataProcessorAsh final : public EventsProcessorInterface {
+ public:
+  // EventsProcessorInterface:
+  bool ShouldProcessOnEventRecord(const Event& event) override;
+  void OnEventsRecord(Event* event) override;
+  void OnProvideIndependentMetrics(
+      ChromeUserMetricsExtension* uma_proto) override;
+
+ private:
+  // Returns whether the device is enrolled.
+  bool IsDeviceEnrolled();
+};
+
+}  // namespace metrics::structured
+
+#endif  // CHROME_BROWSER_METRICS_STRUCTURED_METADATA_PROCESSOR_ASH_H_
diff --git a/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc b/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc
index 3cd4d53..f35c8f2600 100644
--- a/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc
+++ b/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc
@@ -16,13 +16,13 @@
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/login/demo_mode/demo_setup_test_utils.h"
 #include "chrome/browser/ash/login/existing_user_controller.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/ownership/fake_owner_settings_service.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/test/base/fake_gaia_mixin.h"
diff --git a/chrome/browser/new_tab_page/modules/drive/drive_service.cc b/chrome/browser/new_tab_page/modules/drive/drive_service.cc
index 1f8fa4b..ace23af 100644
--- a/chrome/browser/new_tab_page/modules/drive/drive_service.cc
+++ b/chrome/browser/new_tab_page/modules/drive/drive_service.cc
@@ -356,6 +356,8 @@
   }
   base::UmaHistogramEnumeration("NewTabPage.Drive.ItemSuggestRequestResult",
                                 request_result);
+  base::UmaHistogramCounts100("NewTabPage.Drive.FileCount",
+                              document_list.size());
   for (auto& callback : callbacks_) {
     std::move(callback).Run(mojo::Clone(document_list));
   }
diff --git a/chrome/browser/new_tab_page/modules/drive/drive_service_unittest.cc b/chrome/browser/new_tab_page/modules/drive/drive_service_unittest.cc
index a30d239..474b5f5 100644
--- a/chrome/browser/new_tab_page/modules/drive/drive_service_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/drive/drive_service_unittest.cc
@@ -152,6 +152,8 @@
   ASSERT_EQ(1, histogram_tester_.GetBucketCount(
                    "NewTabPage.Drive.ItemSuggestRequestResult",
                    ItemSuggestRequestResult::kContentError));
+  ASSERT_EQ(1,
+            histogram_tester_.GetBucketCount("NewTabPage.Drive.FileCount", 2));
 }
 
 TEST_F(DriveServiceTest, PassesDataToMultipleRequestsToDriveService) {
@@ -254,6 +256,8 @@
   ASSERT_EQ(1, histogram_tester_.GetBucketCount(
                    "NewTabPage.Drive.ItemSuggestRequestResult",
                    ItemSuggestRequestResult::kSuccess));
+  ASSERT_EQ(1,
+            histogram_tester_.GetBucketCount("NewTabPage.Drive.FileCount", 1));
 }
 
 TEST_F(DriveServiceTest, PassesCachedDataIfRequested) {
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service.cc b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service.cc
index 4d95fb6..3ce9e57 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service.cc
@@ -135,12 +135,17 @@
           ntp_features::kNtpHistoryClustersModuleUseModelRanking) &&
       optimization_guide_keyed_service) {
     module_ranker_ = std::make_unique<HistoryClustersModuleRanker>(
-        optimization_guide_keyed_service, category_boostlist_);
+        optimization_guide_keyed_service, cart_service_, category_boostlist_);
   }
 }
 HistoryClustersModuleService::~HistoryClustersModuleService() = default;
 
 void HistoryClustersModuleService::GetClusters(GetClustersCallback callback) {
+  if (!history_clusters_service_->IsJourneysEnabled()) {
+    std::move(callback).Run({});
+    return;
+  }
+
   if (!template_url_service_) {
     std::move(callback).Run({});
     return;
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service_unittest.cc b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service_unittest.cc
index c295089d..4ef03d8 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_service_unittest.cc
@@ -180,6 +180,14 @@
   return SampleCluster(1, srp_visits, non_srp_visits, related_searches);
 }
 
+TEST_F(HistoryClustersModuleServiceTest, GetClustersJourneysNotEnabled) {
+  test_history_clusters_service().SetIsJourneysEnabled(
+      /*is_journeys_enabled=*/false);
+
+  std::vector<history::Cluster> clusters = GetClusters();
+  EXPECT_TRUE(clusters.empty());
+}
+
 TEST_F(HistoryClustersModuleServiceTest, GetClusters) {
   base::HistogramTester histogram_tester;
 
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.cc b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.cc
index b3bff4c..a2ae54a 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.cc
@@ -6,7 +6,10 @@
 
 #include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
+#include "chrome/browser/cart/cart_db.h"
+#include "chrome/browser/cart/cart_service.h"
 #include "chrome/browser/new_tab_page/modules/history_clusters/history_clusters_module_util.h"
+#include "chrome/browser/new_tab_page/new_tab_page_util.h"
 #include "components/history_clusters/core/history_clusters_util.h"
 #include "components/optimization_guide/core/optimization_guide_model_provider.h"
 #include "components/optimization_guide/machine_learning_tflite_buildflags.h"
@@ -19,8 +22,9 @@
 
 HistoryClustersModuleRanker::HistoryClustersModuleRanker(
     optimization_guide::OptimizationGuideModelProvider* model_provider,
+    CartService* cart_service,
     const base::flat_set<std::string>& category_boostlist)
-    : category_boostlist_(category_boostlist) {
+    : cart_service_(cart_service), category_boostlist_(category_boostlist) {
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   if (model_provider) {
     model_handler_ = std::make_unique<HistoryClustersModuleRankingModelHandler>(
@@ -34,12 +38,28 @@
 void HistoryClustersModuleRanker::RankClusters(
     std::vector<history::Cluster> clusters,
     ClustersCallback callback) {
+  if (IsCartModuleEnabled() && cart_service_) {
+    cart_service_->LoadAllActiveCarts(
+        base::BindOnce(&HistoryClustersModuleRanker::OnAllSignalsReady,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(clusters),
+                       std::move(callback)));
+  } else {
+    OnAllSignalsReady(std::move(clusters), std::move(callback),
+                      /*success=*/false, /*active_carts=*/{});
+  }
+}
+
+void HistoryClustersModuleRanker::OnAllSignalsReady(
+    std::vector<history::Cluster> clusters,
+    ClustersCallback callback,
+    bool success,
+    std::vector<CartDB::KeyAndValue> active_carts) {
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   if (model_handler_ && model_handler_->CanExecuteAvailableModel()) {
     std::vector<HistoryClustersModuleRankingSignals> ranking_signals;
     ranking_signals.reserve(clusters.size());
     for (const auto& cluster : clusters) {
-      ranking_signals.emplace_back(category_boostlist_, cluster);
+      ranking_signals.emplace_back(active_carts, category_boostlist_, cluster);
     }
     model_handler_->ExecuteBatch(
         ranking_signals,
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.h b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.h
index 20ed8f7..14461bd 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.h
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.h
@@ -11,6 +11,7 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/cart/cart_db.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/optimization_guide/machine_learning_tflite_buildflags.h"
 
@@ -18,6 +19,7 @@
 class OptimizationGuideModelProvider;
 }  // namespace optimization_guide
 
+class CartService;
 class HistoryClustersModuleRankingModelHandler;
 
 // An object that sorts a list of clusters by likelihood of re-engagement.
@@ -25,6 +27,7 @@
  public:
   HistoryClustersModuleRanker(
       optimization_guide::OptimizationGuideModelProvider* model_provider,
+      CartService* cart_service,
       const base::flat_set<std::string>& category_boostlist);
   ~HistoryClustersModuleRanker();
 
@@ -42,11 +45,20 @@
 #endif
 
  private:
+  // Callback invoked when all signals for ranking are ready.
+  void OnAllSignalsReady(std::vector<history::Cluster> clusters,
+                         ClustersCallback callback,
+                         bool success,
+                         std::vector<CartDB::KeyAndValue> active_carts);
+
   // Runs the fallback heuristic if `model_handler_` is not instantiated or if
   // the model is not available.
   void RunFallbackHeuristic(std::vector<history::Cluster> clusters,
                             ClustersCallback callback);
 
+  // The cart service used to check for active carts.
+  raw_ptr<CartService> cart_service_;
+
   // The category boostlist to use.
   const base::flat_set<std::string> category_boostlist_;
 
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker_unittest.cc b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker_unittest.cc
index 0cf36c9..f7c34a2 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker_unittest.cc
@@ -5,13 +5,18 @@
 #include "chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranker.h"
 
 #include "base/run_loop.h"
+#include "base/test/gmock_callback_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "chrome/browser/cart/cart_service.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/history_clusters/core/clustering_test_utils.h"
 #include "components/history_clusters/core/history_clusters_util.h"
 #include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "components/optimization_guide/machine_learning_tflite_buildflags.h"
 #include "components/search/ntp_features.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -24,6 +29,13 @@
 
 using ::testing::ElementsAre;
 
+class MockCartService : public CartService {
+ public:
+  explicit MockCartService(Profile* profile) : CartService(profile) {}
+
+  MOCK_METHOD1(LoadAllActiveCarts, void(CartDB::LoadCallback callback));
+};
+
 class HistoryClustersModuleRankerTest : public testing::Test {
  public:
   HistoryClustersModuleRankerTest() = default;
@@ -54,7 +66,7 @@
   }
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  content::BrowserTaskEnvironment task_environment_;
 };
 
 TEST_F(HistoryClustersModuleRankerTest, RecencyOnly) {
@@ -96,7 +108,8 @@
 
   base::flat_set<std::string> boost = {};
   auto module_ranker = std::make_unique<HistoryClustersModuleRanker>(
-      /*optimization_guide_model_provider=*/nullptr, boost);
+      /*optimization_guide_model_provider=*/nullptr, /*cart_service=*/nullptr,
+      boost);
   std::vector<history::Cluster> clusters =
       RankClusters(module_ranker.get(), {cluster1, cluster2});
 
@@ -183,7 +196,8 @@
 
   base::flat_set<std::string> boost = {"boosted", "boostedbuthidden"};
   auto module_ranker = std::make_unique<HistoryClustersModuleRanker>(
-      /*optimization_guide_model_provider=*/nullptr, boost);
+      /*optimization_guide_model_provider=*/nullptr, /*cart_service=*/nullptr,
+      boost);
   std::vector<history::Cluster> clusters =
       RankClusters(module_ranker.get(), {cluster1, cluster2, cluster3});
 
@@ -238,8 +252,10 @@
     : public HistoryClustersModuleRankerTest {
  public:
   HistoryClustersModuleRankerWithModelTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        ntp_features::kNtpHistoryClustersModuleUseModelRanking);
+    scoped_feature_list_.InitWithFeatures(
+        {ntp_features::kNtpHistoryClustersModuleUseModelRanking,
+         ntp_features::kNtpChromeCartModule},
+        {});
   }
 
  private:
@@ -288,7 +304,7 @@
       optimization_guide::TestOptimizationGuideModelProvider>();
   base::flat_set<std::string> boost = {};
   auto module_ranker = std::make_unique<HistoryClustersModuleRanker>(
-      model_provider.get(), boost);
+      model_provider.get(), /*cart_service=*/nullptr, boost);
   std::vector<history::Cluster> clusters =
       RankClusters(module_ranker.get(), {cluster1, cluster2});
 
@@ -378,8 +394,19 @@
   base::flat_set<std::string> boost = {"boosted", "boostedbuthidden"};
   auto model_provider = std::make_unique<
       optimization_guide::TestOptimizationGuideModelProvider>();
+  TestingProfile::Builder profile_builder;
+  profile_builder.AddTestingFactory(HistoryServiceFactory::GetInstance(),
+                                    HistoryServiceFactory::GetDefaultFactory());
+  auto testing_profile = profile_builder.Build();
+  auto cart_service = std::make_unique<MockCartService>(testing_profile.get());
+  EXPECT_CALL(*cart_service,
+              LoadAllActiveCarts(base::test::IsNotNullCallback()))
+      .WillOnce(testing::WithArgs<0>(
+          testing::Invoke([&](CartDB::LoadCallback callback) -> void {
+            std::move(callback).Run(true, {});
+          })));
   auto module_ranker = std::make_unique<HistoryClustersModuleRanker>(
-      model_provider.get(), boost);
+      model_provider.get(), cart_service.get(), boost);
   auto model_handler = std::make_unique<FakeModelHandler>(model_provider.get());
   module_ranker->OverrideModelHandlerForTesting(std::move(model_handler));
   std::vector<history::Cluster> clusters =
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler.cc b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler.cc
index cd714287..40680ce2 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler.cc
@@ -31,6 +31,23 @@
           HISTORY_CLUSTERS_MODULE_RANKING_BELONGS_TO_BOOSTED_CATEGORY:
         input_vector.push_back(signals.belongs_to_boosted_category ? 1 : 0);
         break;
+      case new_tab_page::proto::
+          HISTORY_CLUSTERS_MODULE_RANKING_NUM_VISITS_WITH_IMAGE:
+        input_vector.push_back(
+            static_cast<float>(signals.num_visits_with_image));
+        break;
+      case new_tab_page::proto::
+          HISTORY_CLUSTERS_MODULE_RANKING_NUM_TOTAL_VISITS:
+        input_vector.push_back(static_cast<float>(signals.num_total_visits));
+        break;
+      case new_tab_page::proto::
+          HISTORY_CLUSTERS_MODULE_RANKING_NUM_UNIQUE_HOSTS:
+        input_vector.push_back(static_cast<float>(signals.num_unique_hosts));
+        break;
+      case new_tab_page::proto::
+          HISTORY_CLUSTERS_MODULE_RANKING_NUM_ABANDONED_CARTS:
+        input_vector.push_back(static_cast<float>(signals.num_abandoned_carts));
+        break;
       default:
         NOTREACHED();
     }
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler_unittest.cc b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler_unittest.cc
index f685428..fa3cade 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler_unittest.cc
@@ -165,27 +165,67 @@
 
 TEST_F(HistoryClustersModuleRankingModelHandlerTest,
        ModelExecutedMultipleInputs) {
-  new_tab_page::proto::HistoryClustersModuleRankingModelMetadata metadata;
-  metadata.set_version(HistoryClustersModuleRankingSignals::kClientVersion);
-  metadata.add_signals(
-      new_tab_page::proto::
-          HISTORY_CLUSTERS_MODULE_RANKING_MINUTES_SINCE_MOST_RECENT_VISIT);
-  metadata.add_signals(
-      new_tab_page::proto::
-          HISTORY_CLUSTERS_MODULE_RANKING_BELONGS_TO_BOOSTED_CATEGORY);
-  PushModelFileToModelExecutor(metadata);
-
-  EXPECT_TRUE(model_handler()->CanExecuteAvailableModel());
-
   HistoryClustersModuleRankingSignals inputs1;
   inputs1.duration_since_most_recent_visit = base::Minutes(2);
   inputs1.belongs_to_boosted_category = false;
+  inputs1.num_visits_with_image = 3;
+  inputs1.num_total_visits = 4;
+  inputs1.num_unique_hosts = 2;
+  inputs1.num_abandoned_carts = 1;
 
   HistoryClustersModuleRankingSignals inputs2;
   inputs2.duration_since_most_recent_visit = base::Minutes(5);
   inputs2.belongs_to_boosted_category = true;
+  inputs2.num_visits_with_image = 2;
+  inputs2.num_total_visits = 10;
+  inputs2.num_unique_hosts = 3;
+  inputs2.num_abandoned_carts = 0;
 
-  EXPECT_THAT(GetOutputs({inputs1, inputs2}), ElementsAre(2, 5 + 1));
+  {
+    new_tab_page::proto::HistoryClustersModuleRankingModelMetadata metadata;
+    metadata.set_version(HistoryClustersModuleRankingSignals::kClientVersion);
+    metadata.add_signals(
+        new_tab_page::proto::
+            HISTORY_CLUSTERS_MODULE_RANKING_MINUTES_SINCE_MOST_RECENT_VISIT);
+    metadata.add_signals(
+        new_tab_page::proto::
+            HISTORY_CLUSTERS_MODULE_RANKING_BELONGS_TO_BOOSTED_CATEGORY);
+    PushModelFileToModelExecutor(metadata);
+
+    EXPECT_TRUE(model_handler()->CanExecuteAvailableModel());
+
+    EXPECT_THAT(GetOutputs({inputs1, inputs2}), ElementsAre(2 + 0, 5 + 1));
+  }
+
+  {
+    new_tab_page::proto::HistoryClustersModuleRankingModelMetadata metadata;
+    metadata.set_version(HistoryClustersModuleRankingSignals::kClientVersion);
+    metadata.add_signals(
+        new_tab_page::proto::
+            HISTORY_CLUSTERS_MODULE_RANKING_NUM_VISITS_WITH_IMAGE);
+    metadata.add_signals(
+        new_tab_page::proto::HISTORY_CLUSTERS_MODULE_RANKING_NUM_TOTAL_VISITS);
+    PushModelFileToModelExecutor(metadata);
+
+    EXPECT_TRUE(model_handler()->CanExecuteAvailableModel());
+
+    EXPECT_THAT(GetOutputs({inputs1, inputs2}), ElementsAre(3 + 4, 2 + 10));
+  }
+
+  {
+    new_tab_page::proto::HistoryClustersModuleRankingModelMetadata metadata;
+    metadata.set_version(HistoryClustersModuleRankingSignals::kClientVersion);
+    metadata.add_signals(
+        new_tab_page::proto::HISTORY_CLUSTERS_MODULE_RANKING_NUM_UNIQUE_HOSTS);
+    metadata.add_signals(
+        new_tab_page::proto::
+            HISTORY_CLUSTERS_MODULE_RANKING_NUM_ABANDONED_CARTS);
+    PushModelFileToModelExecutor(metadata);
+
+    EXPECT_TRUE(model_handler()->CanExecuteAvailableModel());
+
+    EXPECT_THAT(GetOutputs({inputs1, inputs2}), ElementsAre(2 + 1, 3 + 0));
+  }
 }
 
 }  // namespace
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_metadata.proto b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_metadata.proto
index 3ef334b..fdaebb6 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_metadata.proto
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_metadata.proto
@@ -14,6 +14,15 @@
   HISTORY_CLUSTERS_MODULE_RANKING_MINUTES_SINCE_MOST_RECENT_VISIT = 1;
   // Whether the cluster belongs to a boosted category.
   HISTORY_CLUSTERS_MODULE_RANKING_BELONGS_TO_BOOSTED_CATEGORY = 2;
+  // The number of visits that have an image.
+  HISTORY_CLUSTERS_MODULE_RANKING_NUM_VISITS_WITH_IMAGE = 3;
+  // The number of total visits within the cluster, including ones that are not
+  // shown in the module.
+  HISTORY_CLUSTERS_MODULE_RANKING_NUM_TOTAL_VISITS = 4;
+  // The number of unique hosts for visits contained within the cluster.
+  HISTORY_CLUSTERS_MODULE_RANKING_NUM_UNIQUE_HOSTS = 5;
+  // The number of abandoned carts associated with the cluster.
+  HISTORY_CLUSTERS_MODULE_RANKING_NUM_ABANDONED_CARTS = 6;
 }
 
 message HistoryClustersModuleRankingModelMetadata {
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.cc b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.cc
index 4f1e99c..bb1cc01 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.cc
@@ -4,17 +4,50 @@
 
 #include "chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.h"
 
+#include "components/commerce/core/proto/cart_db_content.pb.h"
 #include "components/history_clusters/core/history_clusters_util.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 
 HistoryClustersModuleRankingSignals::HistoryClustersModuleRankingSignals(
+    const std::vector<CartDB::KeyAndValue>& active_carts,
     const base::flat_set<std::string>& category_boostlist,
     const history::Cluster& cluster)
     : duration_since_most_recent_visit(
           base::Time::Now() -
           cluster.GetMostRecentVisit().annotated_visit.visit_row.visit_time),
       belongs_to_boosted_category(
-          history_clusters::IsClusterInCategories(cluster,
-                                                  category_boostlist)) {}
+          category_boostlist.empty()
+              ? false
+              : history_clusters::IsClusterInCategories(cluster,
+                                                        category_boostlist)),
+      num_total_visits(cluster.visits.size()) {
+  base::flat_set<std::string> hosts;
+  base::flat_set<std::string> cart_tlds;
+  for (const auto& visit : cluster.visits) {
+    if (visit.annotated_visit.visit_row.is_known_to_sync &&
+        visit.annotated_visit.content_annotations.has_url_keyed_image) {
+      num_visits_with_image++;
+    }
+
+    hosts.insert(visit.normalized_url.host());
+
+    if (!active_carts.empty()) {
+      std::string visit_tld =
+          net::registry_controlled_domains::GetDomainAndRegistry(
+              visit.normalized_url,
+              net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+      for (auto cart : active_carts) {
+        if (cart.first == visit_tld) {
+          cart_tlds.insert(visit_tld);
+        }
+      }
+    }
+  }
+
+  num_unique_hosts = hosts.size();
+  num_abandoned_carts = cart_tlds.size();
+}
+
 HistoryClustersModuleRankingSignals::HistoryClustersModuleRankingSignals() =
     default;
 HistoryClustersModuleRankingSignals::~HistoryClustersModuleRankingSignals() =
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.h b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.h
index dafde2b7..ee980ef 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.h
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.h
@@ -5,8 +5,12 @@
 #ifndef CHROME_BROWSER_NEW_TAB_PAGE_MODULES_HISTORY_CLUSTERS_RANKING_HISTORY_CLUSTERS_MODULE_RANKING_SIGNALS_H_
 #define CHROME_BROWSER_NEW_TAB_PAGE_MODULES_HISTORY_CLUSTERS_RANKING_HISTORY_CLUSTERS_MODULE_RANKING_SIGNALS_H_
 
+#include <vector>
+
 #include "base/containers/flat_set.h"
 #include "base/time/time.h"
+#include "chrome/browser/cart/cart_db.h"
+#include "components/commerce/core/proto/cart_db_content.pb.h"
 #include "components/history/core/browser/history_types.h"
 
 // The signals used to rank clusters for the history clusters module.
@@ -16,6 +20,7 @@
 
   // Creates signals from `cluster`.
   HistoryClustersModuleRankingSignals(
+      const std::vector<CartDB::KeyAndValue>& active_carts,
       const base::flat_set<std::string>& category_boostlist,
       const history::Cluster& cluster);
   HistoryClustersModuleRankingSignals();
@@ -27,6 +32,15 @@
   base::TimeDelta duration_since_most_recent_visit;
   // Whether the cluster is of a boosted category.
   bool belongs_to_boosted_category = false;
+  // The number of visits that have an image.
+  size_t num_visits_with_image = 0;
+  // The number of total visits in the cluster including ones that are not
+  // necessarily shown in the module.
+  size_t num_total_visits = 0;
+  // The number of unique hosts represented in the cluster.
+  size_t num_unique_hosts = 0;
+  // The number of abandoned carts associated with the cluster.
+  size_t num_abandoned_carts = 0;
 };
 
 #endif  // CHROME_BROWSER_NEW_TAB_PAGE_MODULES_HISTORY_CLUSTERS_RANKING_HISTORY_CLUSTERS_MODULE_RANKING_SIGNALS_H_
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals_unittest.cc b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals_unittest.cc
new file mode 100644
index 0000000..9c1fe59
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals.h"
+
+#include "components/commerce/core/proto/cart_db_content.pb.h"
+#include "components/history_clusters/core/clustering_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using HistoryClustersModuleRankingSignalsTest = testing::Test;
+
+TEST_F(HistoryClustersModuleRankingSignalsTest, ConstructorNoCartsNoBoost) {
+  history::Cluster cluster;
+  cluster.cluster_id = 1;
+  history::AnnotatedVisit visit =
+      history_clusters::testing::CreateDefaultAnnotatedVisit(
+          1, GURL("https://github.com/"));
+  visit.visit_row.is_known_to_sync = true;
+  visit.content_annotations.has_url_keyed_image = true;
+  visit.content_annotations.model_annotations.categories = {{"category1", 90},
+                                                            {"boosted", 84}};
+  history::AnnotatedVisit visit2 =
+      history_clusters::testing::CreateDefaultAnnotatedVisit(
+          2, GURL("https://search.com/"));
+  visit2.visit_row.visit_time = base::Time::FromTimeT(100);
+  visit2.content_annotations.search_terms = u"search";
+  visit2.content_annotations.related_searches = {"relsearch1", "relsearch2"};
+  history::AnnotatedVisit visit4 =
+      history_clusters::testing::CreateDefaultAnnotatedVisit(
+          4, GURL("https://github.com/2"));
+  visit4.content_annotations.model_annotations.categories = {{"category1", 85},
+                                                             {"category3", 82}};
+  visit4.content_annotations.has_url_keyed_image = true;
+  visit4.visit_row.is_known_to_sync = true;
+  cluster.visits = {history_clusters::testing::CreateClusterVisit(
+                        visit, /*normalized_url=*/absl::nullopt, 1.0),
+                    history_clusters::testing::CreateClusterVisit(
+                        visit2, /*normalized_url=*/absl::nullopt, 1.0),
+                    history_clusters::testing::CreateClusterVisit(
+                        visit4, /*normalized_url=*/absl::nullopt, 0.3)};
+
+  HistoryClustersModuleRankingSignals signals(
+      /*active_carts=*/{}, /*category_boostlist=*/{}, cluster);
+  EXPECT_GT(signals.duration_since_most_recent_visit.InMinutes(), 0);
+  // Even though it says boosted, there is no passed-in boostlist so it's false.
+  EXPECT_FALSE(signals.belongs_to_boosted_category);
+  EXPECT_EQ(signals.num_visits_with_image, 2u);
+  EXPECT_EQ(signals.num_total_visits, 3u);
+  // github.com and search.com
+  EXPECT_EQ(signals.num_unique_hosts, 2u);
+  EXPECT_EQ(signals.num_abandoned_carts, 0u);
+}
+
+TEST_F(HistoryClustersModuleRankingSignalsTest, ConstructorHasCartsAndBoost) {
+  history::Cluster cluster;
+  cluster.cluster_id = 1;
+  history::AnnotatedVisit visit =
+      history_clusters::testing::CreateDefaultAnnotatedVisit(
+          1, GURL("https://m.merchant.com/"));
+  visit.visit_row.is_known_to_sync = true;
+  visit.content_annotations.has_url_keyed_image = true;
+  visit.content_annotations.model_annotations.categories = {{"category1", 90},
+                                                            {"boosted", 84}};
+  history::AnnotatedVisit visit2 =
+      history_clusters::testing::CreateDefaultAnnotatedVisit(
+          2, GURL("https://search.com/"));
+  visit2.visit_row.visit_time = base::Time::FromTimeT(100);
+  visit2.content_annotations.search_terms = u"search";
+  visit2.content_annotations.related_searches = {"relsearch1", "relsearch2"};
+  history::AnnotatedVisit visit4 =
+      history_clusters::testing::CreateDefaultAnnotatedVisit(
+          4, GURL("https://www.merchant.com/2"));
+  visit4.content_annotations.model_annotations.categories = {{"category1", 85},
+                                                             {"category3", 82}};
+  visit4.content_annotations.has_url_keyed_image = true;
+  visit4.visit_row.is_known_to_sync = true;
+  cluster.visits = {history_clusters::testing::CreateClusterVisit(
+                        visit, /*normalized_url=*/absl::nullopt, 1.0),
+                    history_clusters::testing::CreateClusterVisit(
+                        visit2, /*normalized_url=*/absl::nullopt, 1.0),
+                    history_clusters::testing::CreateClusterVisit(
+                        visit4, /*normalized_url=*/absl::nullopt, 0.3)};
+
+  std::vector<CartDB::KeyAndValue> active_carts = {
+      {"merchant.com", cart_db::ChromeCartContentProto::default_instance()},
+  };
+  base::flat_set<std::string> category_boostlist = {"boosted"};
+  HistoryClustersModuleRankingSignals signals(active_carts, category_boostlist,
+                                              cluster);
+  EXPECT_GT(signals.duration_since_most_recent_visit.InMinutes(), 0);
+  EXPECT_TRUE(signals.belongs_to_boosted_category);
+  EXPECT_EQ(signals.num_visits_with_image, 2u);
+  EXPECT_EQ(signals.num_total_visits, 3u);
+  // m.merchant.com, www.merchant.com, and search.com
+  EXPECT_EQ(signals.num_unique_hosts, 3u);
+  // m.merchant.com and www.merchant.com should both match to merchant.com.
+  EXPECT_EQ(signals.num_abandoned_carts, 1u);
+}
+
+}  // namespace
diff --git a/chrome/browser/new_tab_page/new_tab_page_interactive_uitest.cc b/chrome/browser/new_tab_page/new_tab_page_interactive_uitest.cc
index 4d487f4..466419d 100644
--- a/chrome/browser/new_tab_page/new_tab_page_interactive_uitest.cc
+++ b/chrome/browser/new_tab_page/new_tab_page_interactive_uitest.cc
@@ -219,12 +219,9 @@
 // ubsan.
 // TODO(crbug.com/1377330): NewTabPageTest.LandingPagePixelTest is failing on
 // Win11 Tests x64.
-#if (defined(UNDEFINED_SANITIZER) && BUILDFLAG(IS_LINUX)) || BUILDFLAG(IS_WIN)
-#define MAYBE_LandingPagePixelTest DISABLED_LandingPagePixelTest
-#else
-#define MAYBE_LandingPagePixelTest LandingPagePixelTest
-#endif
-IN_PROC_BROWSER_TEST_F(NewTabPageTest, MAYBE_LandingPagePixelTest) {
+// TODO(crbug.com/1416880): It's also found flaky on Linux Tests, Linux Tests
+// (Wayland), linux-lacros-tester-rel, Mac12 Tests. Disabling on all platforms.
+IN_PROC_BROWSER_TEST_F(NewTabPageTest, DISABLED_LandingPagePixelTest) {
   WaitForLazyLoad();
   // By default WaitForNetworkLoad waits for all resources that have started
   // loading at this point. However, sometimes not all required resources have
diff --git a/chrome/browser/offline_pages/recent_tab_helper.h b/chrome/browser/offline_pages/recent_tab_helper.h
index dbea527b..9567d33 100644
--- a/chrome/browser/offline_pages/recent_tab_helper.h
+++ b/chrome/browser/offline_pages/recent_tab_helper.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "components/offline_pages/core/offline_page_model.h"
diff --git a/chrome/browser/password_manager/android/password_generation_controller_impl.cc b/chrome/browser/password_manager/android/password_generation_controller_impl.cc
index ded095a..8e7816c7 100644
--- a/chrome/browser/password_manager/android/password_generation_controller_impl.cc
+++ b/chrome/browser/password_manager/android/password_generation_controller_impl.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/password_manager/android/password_generation_dialog_view_interface.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/touch_to_fill/password_generation/android/touch_to_fill_password_generation_controller.h"
+#include "components/autofill/core/browser/ui/accessory_sheet_enums.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/password_generation_util.h"
 #include "components/autofill/core/common/signatures.h"
@@ -32,6 +33,7 @@
 using autofill::mojom::FocusedFieldType;
 using autofill::password_generation::PasswordGenerationType;
 using password_manager::metrics_util::GenerationDialogChoice;
+using ShouldShowAction = ManualFillingController::ShouldShowAction;
 
 PasswordGenerationControllerImpl::~PasswordGenerationControllerImpl() = default;
 
@@ -128,7 +130,9 @@
   }
 
   DCHECK(manual_filling_controller_);
-  manual_filling_controller_->OnAutomaticGenerationStatusChanged(true);
+  manual_filling_controller_->OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction(true),
+      autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC);
 }
 
 void PasswordGenerationControllerImpl::ShowManualGenerationDialog(
@@ -274,7 +278,9 @@
 
 void PasswordGenerationControllerImpl::ResetFocusState() {
   if (manual_filling_controller_)
-    manual_filling_controller_->OnAutomaticGenerationStatusChanged(false);
+    manual_filling_controller_->OnAccessoryActionAvailabilityChanged(
+        ShouldShowAction(false),
+        autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC);
   active_frame_driver_.reset();
   generation_element_data_.reset();
   dialog_view_.reset();
diff --git a/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc b/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc
index f591b1f..dbb560c 100644
--- a/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc
+++ b/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc
@@ -17,6 +17,7 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/ui/accessory_sheet_enums.h"
 #include "components/autofill/core/common/password_generation_util.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "components/password_manager/core/browser/mock_password_store_interface.h"
@@ -50,6 +51,7 @@
 using testing::NiceMock;
 using testing::Return;
 using testing::StrictMock;
+using ShouldShowAction = ManualFillingController::ShouldShowAction;
 
 class TestPasswordManagerClient
     : public password_manager::StubPasswordManagerClient {
@@ -166,7 +168,9 @@
         std::make_unique<NiceMock<MockPasswordGenerationDialogView>>();
 
     EXPECT_CALL(mock_manual_filling_controller_,
-                OnAutomaticGenerationStatusChanged(false));
+                OnAccessoryActionAvailabilityChanged(
+                    ShouldShowAction(false),
+                    autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
     controller()->FocusedInputChanged(
         FocusedFieldType::kFillablePasswordField,
         base::AsWeakPtr(password_manager_driver_.get()));
@@ -222,7 +226,9 @@
 
 TEST_F(PasswordGenerationControllerTest, RelaysAutomaticGenerationAvailable) {
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(true));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(true),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->OnAutomaticGenerationAvailable(
       active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20));
 }
@@ -234,7 +240,9 @@
        UpdatesSignaturesForDifferentGenerationForms) {
   // Called twice for different forms.
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(true))
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(true),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC))
       .Times(2);
   controller()->OnAutomaticGenerationAvailable(
       active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20));
@@ -265,12 +273,16 @@
   base::HistogramTester histogram_tester;
 
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(true));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(true),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->OnAutomaticGenerationAvailable(
       active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20));
 
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->GeneratedPasswordAccepted(u"t3stp@ssw0rd", active_driver(),
                                           PasswordGenerationType::kAutomatic);
 
@@ -284,7 +296,9 @@
   base::HistogramTester histogram_tester;
 
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->GeneratedPasswordRejected(PasswordGenerationType::kAutomatic);
 
   histogram_tester.ExpectUniqueSample(
@@ -304,7 +318,9 @@
                                            GetTestGenerationUIData1());
 
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->GeneratedPasswordAccepted(u"t3stp@ssw0rd", active_driver(),
                                           PasswordGenerationType::kManual);
 
@@ -318,7 +334,9 @@
   base::HistogramTester histogram_tester;
 
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->GeneratedPasswordRejected(PasswordGenerationType::kManual);
 
   histogram_tester.ExpectUniqueSample(
@@ -329,11 +347,12 @@
 TEST_F(PasswordGenerationControllerTest,
        SetActiveFrameOnAutomaticGenerationAvailable) {
   // TODO(crbug.com/1421753): Refactor PasswordGenerationController so that
-  // OnAutomaticGenerationStatusChanged would be called only once. Right now
+  // OnAccessoryActionAvailabilityChanged would be called only once. Right now
   // it's called twice: the first call resets the manual filling controller
   // status and the second one sets it according to the focused input.
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(_))
+              OnAccessoryActionAvailabilityChanged(
+                  _, autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC))
       .Times(AtMost(2));
 
   controller()->OnAutomaticGenerationAvailable(
@@ -343,7 +362,9 @@
 TEST_F(PasswordGenerationControllerTest,
        ResetStateWhenFocusChangesToNonPassword) {
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
 
   controller()->FocusedInputChanged(FocusedFieldType::kFillableUsernameField,
                                     active_driver());
@@ -353,7 +374,9 @@
 TEST_F(PasswordGenerationControllerTest,
        ResetStateWhenFocusChangesToOtherFramePassword) {
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
 
   controller()->FocusedInputChanged(FocusedFieldType::kFillablePasswordField,
                                     non_active_driver());
@@ -374,7 +397,9 @@
   controller()->ShowManualGenerationDialog(password_manager_driver_.get(),
                                            GetTestGenerationUIData1());
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   EXPECT_CALL(*raw_dialog_view, Destroy());
   controller()->FocusedInputChanged(FocusedFieldType::kFillableUsernameField,
                                     non_active_driver());
@@ -425,7 +450,9 @@
   controller()->OnGenerationRequested(PasswordGenerationType::kManual);
 
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged(false));
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(false),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC));
   controller()->FocusedInputChanged(FocusedFieldType::kFillablePasswordField,
                                     non_active_driver());
   EXPECT_CALL(mock_dialog_factory(), Run).Times(0);
@@ -441,7 +468,9 @@
 
   // Keyboard accessory shouldn't be called.
   EXPECT_CALL(mock_manual_filling_controller_,
-              OnAutomaticGenerationStatusChanged)
+              OnAccessoryActionAvailabilityChanged(
+                  ShouldShowAction(true),
+                  autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC))
       .Times(0);
   controller()->OnAutomaticGenerationAvailable(
       active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20));
diff --git a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
index 65302e1..23027ac 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
@@ -237,7 +237,7 @@
   }
 
 #if BUILDFLAG(IS_ANDROID)
-  void TouchToFillClosed(bool show_virtual_keyboard) override {}
+  void KeyboardReplacingSurfaceClosed(bool show_virtual_keyboard) override {}
 
   void TriggerFormSubmission() override {}
 #endif
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index ad5ff66..f1209588 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -3845,8 +3845,7 @@
                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 }
 
-// Test if |PasswordManager.FormVisited.PerProfileType| and
-// |PasswordManager.FormSubmission.PerProfileType| metrics are recorded as
+// Test if |PasswordManager.FormVisited.PerProfileType| metrics are recorded as
 // expected.
 IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
                        ProfileTypeMetricSubmission) {
@@ -3858,8 +3857,6 @@
   histogram_tester.ExpectUniqueSample(
       "PasswordManager.FormVisited.PerProfileType",
       profile_metrics::BrowserProfileType::kRegular, 1);
-  histogram_tester.ExpectTotalCount(
-      "PasswordManager.FormSubmission.PerProfileType", 0);
 
   // Fill a form and submit through a <input type="submit"> button. Nothing
   // special.
@@ -3870,11 +3867,6 @@
       "document.getElementById('input_submit_button').click()";
   ASSERT_TRUE(content::ExecJs(WebContents(), kFillAndSubmit));
   ASSERT_TRUE(observer.Wait());
-
-  // Test if submission is properly recorded.
-  histogram_tester.ExpectUniqueSample(
-      "PasswordManager.FormSubmission.PerProfileType",
-      profile_metrics::BrowserProfileType::kRegular, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(PasswordManagerBackForwardCacheBrowserTest,
@@ -4224,7 +4216,7 @@
               (override));
 #if BUILDFLAG(IS_ANDROID)
   MOCK_METHOD(void,
-              ShowTouchToFill,
+              ShowKeyboardReplacingSurface,
               (autofill::mojom::SubmissionReadinessState),
               (override));
 #endif
@@ -4296,10 +4288,10 @@
                                          options, bounds);
         });
 #if BUILDFLAG(IS_ANDROID)
-    ON_CALL(*this, ShowTouchToFill)
+    ON_CALL(*this, ShowKeyboardReplacingSurface)
         .WillByDefault([this](autofill::mojom::SubmissionReadinessState
                                   submission_readiness) {
-          impl_->ShowTouchToFill(submission_readiness);
+          impl_->ShowKeyboardReplacingSurface(submission_readiness);
         });
 #endif
     ON_CALL(*this, CheckSafeBrowsingReputation)
diff --git a/chrome/browser/pdf/pdf_extension_accessibility_test.cc b/chrome/browser/pdf/pdf_extension_accessibility_test.cc
index 3d19b92..db8b787 100644
--- a/chrome/browser/pdf/pdf_extension_accessibility_test.cc
+++ b/chrome/browser/pdf/pdf_extension_accessibility_test.cc
@@ -16,11 +16,16 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/branding_buildflags.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/accessibility/accessibility_state_utils.h"
 #include "chrome/browser/pdf/pdf_extension_test_base.h"
 #include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/metrics/content/subprocess_metrics_provider.h"
+#include "components/services/screen_ai/buildflags/buildflags.h"
 #include "components/zoom/zoom_controller.h"
 #include "content/public/browser/ax_inspect_factory.h"
 #include "content/public/browser/browser_accessibility_state.h"
@@ -28,10 +33,12 @@
 #include "content/public/test/accessibility_notification_waiter.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "pdf/pdf_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/context_menu_data/untrustworthy_context_menu_params.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_enum_util.h"
 #include "ui/accessibility/ax_node.h"
@@ -43,6 +50,10 @@
 #include "ui/accessibility/platform/inspect/ax_inspect_test_helper.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#include "chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h"
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+
 namespace {
 
 using ::content::WebContents;
@@ -266,10 +277,10 @@
   ASSERT_TRUE(guest);
 
   WebContents* contents = GetActiveWebContents();
-  ASSERT_TRUE(content::ExecuteScript(
-      contents,
-      "document.getElementsByTagName('embed')[0].postMessage("
-      "{type: 'selectAll'});"));
+  ASSERT_TRUE(
+      content::ExecJs(contents,
+                      "document.getElementsByTagName('embed')[0].postMessage("
+                      "{type: 'selectAll'});"));
 
   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
   WaitForAccessibilityTreeToContainNodeWithName(contents,
@@ -326,10 +337,10 @@
   ASSERT_TRUE(guest);
 
   WebContents* contents = GetActiveWebContents();
-  ASSERT_TRUE(content::ExecuteScript(
-      contents,
-      "document.getElementsByTagName('embed')[0].postMessage("
-      "{type: 'selectAll'});"));
+  ASSERT_TRUE(
+      content::ExecJs(contents,
+                      "document.getElementsByTagName('embed')[0].postMessage("
+                      "{type: 'selectAll'});"));
 
   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
   WaitForAccessibilityTreeToContainNodeWithName(contents,
@@ -876,3 +887,68 @@
   const GURL& expected_url = GetActiveWebContents()->GetLastCommittedURL();
   EXPECT_EQ("https://bing.com/", expected_url.spec());
 }
+
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+// This test suite contains simple tests for the PDF OCR feature.
+class PDFExtensionAccessibilityPdfOcrTest
+    : public PDFExtensionAccessibilityTest {
+ public:
+  PDFExtensionAccessibilityPdfOcrTest() = default;
+  ~PDFExtensionAccessibilityPdfOcrTest() override = default;
+
+ protected:
+  std::vector<base::test::FeatureRef> GetEnabledFeatures() const override {
+    auto enabled = PDFExtensionAccessibilityTest::GetEnabledFeatures();
+    enabled.push_back(::features::kPdfOcr);
+    return enabled;
+  }
+
+  void ClickPdfOcrToggleButton(MimeHandlerViewGuest* guest_view) {
+    content::RenderFrameHost* guest_main_frame =
+        guest_view->GetGuestMainFrame();
+    ASSERT_TRUE(guest_main_frame);
+
+    ASSERT_TRUE(content::ExecJs(
+        guest_main_frame,
+        "viewer.shadowRoot.getElementById('toolbar').shadowRoot."
+        "getElementById('pdf-ocr-button').click();"));
+    ASSERT_TRUE(content::WaitForRenderFrameReady(guest_main_frame));
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PDFExtensionAccessibilityPdfOcrTest,
+                       CheckUmaWhenTurnOnPdfOcrFromMoreActions) {
+  MimeHandlerViewGuest* guest_view = LoadPdfGetMimeHandlerView(
+      embedded_test_server()->GetURL("/pdf/test.pdf"));
+  ASSERT_TRUE(guest_view);
+
+  // Turn on PDF OCR always.
+  base::HistogramTester histograms;
+  ClickPdfOcrToggleButton(guest_view);
+
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  histograms.ExpectUniqueSample(
+      "Accessibility.PdfOcr.UserSelection",
+      PdfOcrUserSelection::kTurnOnAlwaysFromMoreActions,
+      /*expected_bucket_count=*/1);
+}
+
+IN_PROC_BROWSER_TEST_F(PDFExtensionAccessibilityPdfOcrTest,
+                       CheckUmaWhenTurnOffPdfOcrFromMoreActions) {
+  MimeHandlerViewGuest* guest_view = LoadPdfGetMimeHandlerView(
+      embedded_test_server()->GetURL("/pdf/test.pdf"));
+  ASSERT_TRUE(guest_view);
+
+  // Turn on PDF OCR always.
+  ClickPdfOcrToggleButton(guest_view);
+
+  // Turn off PDF OCR.
+  base::HistogramTester histograms;
+  ClickPdfOcrToggleButton(guest_view);
+
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  histograms.ExpectUniqueSample("Accessibility.PdfOcr.UserSelection",
+                                PdfOcrUserSelection::kTurnOffFromMoreActions,
+                                /*expected_bucket_count=*/1);
+}
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
diff --git a/chrome/browser/pdf/pdf_extension_js_test.cc b/chrome/browser/pdf/pdf_extension_js_test.cc
index 1949387..4c95736 100644
--- a/chrome/browser/pdf/pdf_extension_js_test.cc
+++ b/chrome/browser/pdf/pdf_extension_js_test.cc
@@ -93,7 +93,7 @@
            };
            document.body.appendChild(s);)";
 
-    ASSERT_TRUE(content::ExecuteScript(
+    ASSERT_TRUE(content::ExecJs(
         guest->GetGuestMainFrame(),
         base::StringPrintf(kModuleLoaderTemplate,
                            chrome::kChromeUIWebUITestHost, filename.c_str())));
diff --git a/chrome/browser/pdf/pdf_extension_printing_test.cc b/chrome/browser/pdf/pdf_extension_printing_test.cc
index 86ff834..85f0d96 100644
--- a/chrome/browser/pdf/pdf_extension_printing_test.cc
+++ b/chrome/browser/pdf/pdf_extension_printing_test.cc
@@ -241,8 +241,7 @@
         .shadowRoot.querySelector('#print')
         .click();
   )";
-  EXPECT_TRUE(
-      ExecuteScript(guest->GetGuestMainFrame(), kClickPrintButtonScript));
+  EXPECT_TRUE(ExecJs(guest->GetGuestMainFrame(), kClickPrintButtonScript));
   print_observer.WaitForPrintPreview();
 }
 #endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 550a513..baa09936 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -994,13 +994,13 @@
 #if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_MAC)
   EXPECT_FALSE(ZoomBubbleView::GetZoomBubble());
 #endif
-  ASSERT_TRUE(content::ExecuteScript(guest_view->GetGuestMainFrame(),
-                                     "while (viewer.viewport.getZoom() < 1) {"
-                                     "  viewer.viewport.zoomIn();"
-                                     "}"
-                                     "setTimeout(() => {"
-                                     "  viewer.viewport.zoomIn();"
-                                     "}, 1);"));
+  ASSERT_TRUE(content::ExecJs(guest_view->GetGuestMainFrame(),
+                              "while (viewer.viewport.getZoom() < 1) {"
+                              "  viewer.viewport.zoomIn();"
+                              "}"
+                              "setTimeout(() => {"
+                              "  viewer.viewport.zoomIn();"
+                              "}, 1);"));
 
   watcher.Wait();
 #if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_MAC)
@@ -1337,11 +1337,10 @@
   content::TestNavigationObserver active_navigation_observer(
       active_web_contents);
   content::TestNavigationObserver navigation_observer(web_contents);
-  ASSERT_TRUE(
-      content::ExecuteScript(guest->GetGuestMainFrame(),
-                             "viewer.navigator_.navigate("
-                             "    'www.example.com',"
-                             "    WindowOpenDisposition.CURRENT_TAB);"));
+  ASSERT_TRUE(content::ExecJs(guest->GetGuestMainFrame(),
+                              "viewer.navigator_.navigate("
+                              "    'www.example.com',"
+                              "    WindowOpenDisposition.CURRENT_TAB);"));
   navigation_observer.Wait();
 
   EXPECT_FALSE(navigation_observer.last_navigation_url().is_empty());
@@ -1603,7 +1602,7 @@
   // Click on the link which opens the PDF via JS.
   content::TestNavigationObserver navigation_observer(web_contents);
   const char kPdfLinkClick[] = "document.getElementById('link').click();";
-  ASSERT_TRUE(content::ExecuteScript(web_contents, kPdfLinkClick));
+  ASSERT_TRUE(content::ExecJs(web_contents, kPdfLinkClick));
   navigation_observer.Wait();
   const GURL& current_url = web_contents->GetLastCommittedURL();
   ASSERT_EQ("/pdf/test-link.pdf", current_url.path());
@@ -1661,9 +1660,9 @@
   MimeHandlerViewGuest* guest = LoadTestLinkPdfGetMimeHandlerView();
   ASSERT_TRUE(guest);
   ASSERT_TRUE(
-      content::ExecuteScript(guest->GetGuestMainFrame(),
-                             "window.viewer.browserApi.getStreamInfo().tabId = "
-                             "    chrome.tabs.TAB_ID_NONE;"));
+      content::ExecJs(guest->GetGuestMainFrame(),
+                      "window.viewer.browserApi.getStreamInfo().tabId = "
+                      "    chrome.tabs.TAB_ID_NONE;"));
 
   FailOnNavigation fail_if_mimehandler_navigates(guest->web_contents());
   SimulateMouseClickAt(guest, blink::WebInputEvent::kNoModifiers,
@@ -1884,7 +1883,7 @@
   }
 
   void SaveEditedPdf(MimeHandlerViewGuest* guest) {
-    ASSERT_TRUE(content::ExecuteScript(
+    ASSERT_TRUE(content::ExecJs(
         guest->GetGuestMainFrame(),
         "var viewer = document.getElementById('viewer');"
         "var toolbar = viewer.shadowRoot.getElementById('toolbar');"
@@ -2280,12 +2279,12 @@
                                   base::OnceClosure send_events) {
   constexpr char kListenPinchUpdate[] = R"(
       const gestureDetector = viewer.viewport.getGestureDetectorForTesting();
-      const updatePromise = new Promise((resolve) => {
+      var updatePromise = new Promise((resolve) => {
         gestureDetector.getEventTarget().addEventListener('pinchupdate',
                                                           resolve);
       });
   )";
-  ASSERT_TRUE(content::ExecuteScript(guest_mainframe, kListenPinchUpdate));
+  ASSERT_TRUE(content::ExecJs(guest_mainframe, kListenPinchUpdate));
 
   zoom::ZoomChangedWatcher zoom_watcher(
       contents,
@@ -2416,16 +2415,16 @@
   gfx::Point point_in_pdf(250, 250);
 
   // Inject script to count MouseLeaves in the PDF.
-  ASSERT_TRUE(content::ExecuteScript(
-      guest_mainframe,
-      "var enter_count = 0;\n"
-      "var leave_count = 0;\n"
-      "document.addEventListener('mouseenter', function (){\n"
-      "  enter_count++;"
-      "});\n"
-      "document.addEventListener('mouseleave', function (){\n"
-      "  leave_count++;"
-      "});"));
+  ASSERT_TRUE(
+      content::ExecJs(guest_mainframe,
+                      "var enter_count = 0;\n"
+                      "var leave_count = 0;\n"
+                      "document.addEventListener('mouseenter', function (){\n"
+                      "  enter_count++;"
+                      "});\n"
+                      "document.addEventListener('mouseleave', function (){\n"
+                      "  leave_count++;"
+                      "});"));
 
   // Inject some MouseMoves to invoke a MouseLeave in the PDF.
   WebContents* embedder_contents = GetActiveWebContents();
@@ -2722,9 +2721,8 @@
   ASSERT_TRUE(guest_mainframe);
 
   // Set focus on last toolbar element (zoom-out-button).
-  ASSERT_TRUE(
-      content::ExecuteScript(guest_mainframe,
-                             R"(viewer.shadowRoot.querySelector('#zoomToolbar')
+  ASSERT_TRUE(content::ExecJs(guest_mainframe,
+                              R"(viewer.shadowRoot.querySelector('#zoomToolbar')
          .$['zoom-out-button']
          .$$('cr-icon-button')
          .focus();)"));
@@ -2743,7 +2741,7 @@
       window.domAutomationController.send('zoom-out-button');
     });
   )";
-  ASSERT_TRUE(content::ExecuteScript(guest_mainframe, kScript));
+  ASSERT_TRUE(content::ExecJs(guest_mainframe, kScript));
 
   // Helper to simulate a tab press and wait for a focus message.
   auto press_tab_and_wait_for_message = [guest_mainframe, this](bool reverse) {
diff --git a/chrome/browser/pdf/pdf_extension_test_base.cc b/chrome/browser/pdf/pdf_extension_test_base.cc
index ce01e8f5..03b7a72 100644
--- a/chrome/browser/pdf/pdf_extension_test_base.cc
+++ b/chrome/browser/pdf/pdf_extension_test_base.cc
@@ -134,9 +134,8 @@
 
   // Reach into the guest and hook into it such that it posts back a 'flush'
   // message after every getSelectedTextReply message sent.
-  ASSERT_TRUE(
-      content::ExecuteScript(guest->GetGuestMainFrame(),
-                             "viewer.overrideSendScriptingMessageForTest();"));
+  ASSERT_TRUE(content::ExecJs(guest->GetGuestMainFrame(),
+                              "viewer.overrideSendScriptingMessageForTest();"));
 
   // Add an event listener for flush messages and request the selected text.
   // If we get a flush message without receiving getSelectedText we know that
diff --git a/chrome/browser/pdf/pdf_extension_test_util.cc b/chrome/browser/pdf/pdf_extension_test_util.cc
index b9f27bf..82f8d75 100644
--- a/chrome/browser/pdf/pdf_extension_test_util.cc
+++ b/chrome/browser/pdf/pdf_extension_test_util.cc
@@ -103,7 +103,7 @@
     ADD_FAILURE() << "The guest main frame needs to be non-null";
     return point;
   }
-  if (!content::ExecuteScript(
+  if (!content::ExecJs(
           guest_main_frame,
           "var visiblePage = viewer.viewport.getMostVisiblePage();"
           "var visiblePageDimensions ="
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
index c32dade..b0836f7 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
@@ -171,16 +171,13 @@
   graph->PassToGraph(
       std::make_unique<performance_manager::policies::PageFreezingPolicy>());
 
-  if (base::FeatureList::IsEnabled(
-          performance_manager::features::kHeuristicMemorySaver)) {
-    graph->PassToGraph(
-        std::make_unique<
-            performance_manager::policies::HeuristicMemorySaverPolicy>());
-  } else {
-    graph->PassToGraph(
-        std::make_unique<
-            performance_manager::policies::HighEfficiencyModePolicy>());
-  }
+  // Add both policies. Only one will be enabled at a time.
+  graph->PassToGraph(
+      std::make_unique<
+          performance_manager::policies::HeuristicMemorySaverPolicy>());
+  graph->PassToGraph(
+      std::make_unique<
+          performance_manager::policies::HighEfficiencyModePolicy>());
 #endif  // !BUILDFLAG(IS_ANDROID)
 
   graph->PassToGraph(
diff --git a/chrome/browser/performance_manager/metrics/metrics_provider_desktop_unittest.cc b/chrome/browser/performance_manager/metrics/metrics_provider_desktop_unittest.cc
index f0019f4c..aa0342b 100644
--- a/chrome/browser/performance_manager/metrics/metrics_provider_desktop_unittest.cc
+++ b/chrome/browser/performance_manager/metrics/metrics_provider_desktop_unittest.cc
@@ -15,14 +15,6 @@
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-class FakeHighEfficiencyModeDelegate
-    : public performance_manager::user_tuning::UserPerformanceTuningManager::
-          HighEfficiencyModeDelegate {
- public:
-  void ToggleHighEfficiencyMode(bool enabled) override {}
-  ~FakeHighEfficiencyModeDelegate() override = default;
-};
-
 class PerformanceManagerMetricsProviderDesktopTest : public testing::Test {
  protected:
   PrefService* local_state() { return &local_state_; }
diff --git a/chrome/browser/performance_manager/policies/heuristic_memory_saver_browsertest.cc b/chrome/browser/performance_manager/policies/heuristic_memory_saver_browsertest.cc
index 49bd50a..6a4626d 100644
--- a/chrome/browser/performance_manager/policies/heuristic_memory_saver_browsertest.cc
+++ b/chrome/browser/performance_manager/policies/heuristic_memory_saver_browsertest.cc
@@ -5,28 +5,37 @@
 #include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.h"
+#include "chrome/browser/performance_manager/policies/high_efficiency_mode_policy.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/performance_manager/public/features.h"
 #include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
 
-class HeuristicMemorySaverBrowserTest : public InProcessBrowserTest {
+class HeuristicMemorySaverBrowserTest
+    : public InProcessBrowserTest,
+      public ::testing::WithParamInterface<bool> {
  public:
   HeuristicMemorySaverBrowserTest() {
-    features_.InitAndEnableFeature(
-        performance_manager::features::kHeuristicMemorySaver);
+    features_.InitWithFeatureState(features::kHeuristicMemorySaver, GetParam());
   }
-  ~HeuristicMemorySaverBrowserTest() override = default;
 
   base::test::ScopedFeatureList features_;
 };
 
-IN_PROC_BROWSER_TEST_F(HeuristicMemorySaverBrowserTest, StartManager) {
+INSTANTIATE_TEST_SUITE_P(All,
+                         HeuristicMemorySaverBrowserTest,
+                         ::testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(HeuristicMemorySaverBrowserTest, StartManager) {
   // This test checks that the UserPerformanceTuningManager can start properly
-  // with the kHeuristicMemorySaver feature enabled. Previously, it would
-  // attempt to set the discard time for the HighEfficiencyModePolicy which
-  // would crash because it is not initialized when kHeuristicMemorySaver is on.
+  // with and without the kHeuristicMemorySaver feature enabled. In both states
+  // the same policies should be created, so that when the multistate UI is
+  // enabled it can switch between them.
+  EXPECT_TRUE(policies::HighEfficiencyModePolicy::GetInstance());
+  EXPECT_TRUE(policies::HeuristicMemorySaverPolicy::GetInstance());
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.cc b/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.cc
index b39bbcaf..baf845ad 100644
--- a/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.cc
+++ b/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.cc
@@ -14,6 +14,7 @@
 #include "base/process/process_metrics.h"
 #include "base/system/sys_info.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "chrome/browser/performance_manager/policies/page_discarding_helper.h"
 #include "components/performance_manager/public/features.h"
 
@@ -23,7 +24,50 @@
 
 HeuristicMemorySaverPolicy* g_heuristic_memory_saver_policy = nullptr;
 
-const uint64_t kBytesPerMb = 1024 * 1024;
+constexpr uint64_t kBytesPerMb = 1024 * 1024;
+
+base::TimeDelta GetThresholdReachedHeartbeatInterval() {
+  constexpr base::TimeDelta kDefault = base::Seconds(10);
+  base::TimeDelta interval =
+      features::kHeuristicMemorySaverThresholdReachedHeartbeatInterval.Get();
+  return interval.is_zero() ? kDefault : interval;
+}
+
+base::TimeDelta GetThresholdNotReachedHeartbeatInterval() {
+  constexpr base::TimeDelta kDefault = base::Seconds(60);
+  base::TimeDelta interval =
+      features::kHeuristicMemorySaverThresholdNotReachedHeartbeatInterval.Get();
+  return interval.is_zero() ? kDefault : interval;
+}
+
+base::TimeDelta GetMinimumTimeInBackground() {
+  constexpr base::TimeDelta kDefault = base::Hours(2);
+  base::TimeDelta delta =
+      features::kHeuristicMemorySaverMinimumTimeInBackground.Get();
+  return delta.is_zero() ? kDefault : delta;
+}
+
+int GetAvailableMemoryThresholdPercent() {
+  constexpr int kDefault = 5;
+  int threshold =
+      features::kHeuristicMemorySaverAvailableMemoryThresholdPercent.Get();
+  return threshold < 0 ? kDefault : threshold;
+}
+
+int GetAvailableMemoryThresholdMb() {
+  constexpr int kDefault = 4096;
+  int threshold =
+      features::kHeuristicMemorySaverAvailableMemoryThresholdMb.Get();
+  return threshold < 0 ? kDefault : threshold;
+}
+
+#if BUILDFLAG(IS_MAC)
+int GetPageCacheDiscountMac() {
+  constexpr int kDefault = 50;
+  int discount = features::kHeuristicMemorySaverPageCacheDiscountMac.Get();
+  return discount < 0 ? kDefault : discount;
+}
+#endif
 
 }  // namespace
 
@@ -63,8 +107,7 @@
     // Start the first timer as if the threshold was reached, memory will be
     // sampled in the callback and the next timer will be scheduled with the
     // appropriate interval.
-    ScheduleNextHeartbeat(
-        features::kHeuristicMemorySaverThresholdReachedHeartbeatInterval.Get());
+    ScheduleNextHeartbeat(GetThresholdReachedHeartbeatInterval());
   } else {
     heartbeat_timer_.Stop();
   }
@@ -74,20 +117,34 @@
   return is_active_;
 }
 
+base::TimeDelta
+HeuristicMemorySaverPolicy::GetThresholdReachedHeartbeatIntervalForTesting()
+    const {
+  return GetThresholdReachedHeartbeatInterval();
+}
+
+base::TimeDelta
+HeuristicMemorySaverPolicy::GetThresholdNotReachedHeartbeatIntervalForTesting()
+    const {
+  return GetThresholdNotReachedHeartbeatInterval();
+}
+
+base::TimeDelta
+HeuristicMemorySaverPolicy::GetMinimumTimeInBackgroundForTesting() const {
+  return GetMinimumTimeInBackground();
+}
+
 void HeuristicMemorySaverPolicy::OnHeartbeatCallback() {
   const uint64_t available_memory = available_memory_cb_.Run();
   const uint64_t total_physical_memory = total_memory_cb_.Run();
 
-  base::TimeDelta next_interval =
-      features::kHeuristicMemorySaverThresholdNotReachedHeartbeatInterval.Get();
+  base::TimeDelta next_interval = GetThresholdNotReachedHeartbeatInterval();
 
-  const int pmf_threshold_percent =
-      features::kHeuristicMemorySaverAvailableMemoryThresholdPercent.Get();
+  const int pmf_threshold_percent = GetAvailableMemoryThresholdPercent();
   CHECK_LE(0, pmf_threshold_percent);
   CHECK_LE(pmf_threshold_percent, 100);
   const uint64_t pmf_threshold_bytes =
-      features::kHeuristicMemorySaverAvailableMemoryThresholdMb.Get() *
-      kBytesPerMb;
+      GetAvailableMemoryThresholdMb() * kBytesPerMb;
   if (available_memory < pmf_threshold_bytes &&
       static_cast<float>(available_memory) /
               static_cast<float>(total_physical_memory) * 100.f <
@@ -95,9 +152,8 @@
     PageDiscardingHelper::GetFromGraph(graph_)->DiscardAPage(
         /*post_discard_cb=*/base::DoNothing(),
         PageDiscardingHelper::DiscardReason::PROACTIVE,
-        features::kHeuristicMemorySaverMinimumTimeInBackground.Get());
-    next_interval =
-        features::kHeuristicMemorySaverThresholdReachedHeartbeatInterval.Get();
+        GetMinimumTimeInBackground());
+    next_interval = GetThresholdReachedHeartbeatInterval();
   }
 
   ScheduleNextHeartbeat(next_interval);
@@ -142,8 +198,7 @@
   constexpr uint64_t kBytesPerKb = 1024;
   base::SystemMemoryInfoKB info;
   if (base::GetSystemMemoryInfo(&info)) {
-    const int available_page_cache_percent =
-        features::kHeuristicMemorySaverPageCacheDiscountMac.Get();
+    const int available_page_cache_percent = GetPageCacheDiscountMac();
     CHECK_GE(available_page_cache_percent, 0);
     CHECK_LE(available_page_cache_percent, 100);
     available_memory += (info.file_backed * kBytesPerKb *
diff --git a/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.h b/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.h
index 5fb320b..b765334 100644
--- a/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.h
+++ b/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy.h
@@ -8,6 +8,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "components/performance_manager/public/graph/graph.h"
 
@@ -64,6 +65,10 @@
   void SetActive(bool enabled);
   bool IsActive() const;
 
+  base::TimeDelta GetThresholdReachedHeartbeatIntervalForTesting() const;
+  base::TimeDelta GetThresholdNotReachedHeartbeatIntervalForTesting() const;
+  base::TimeDelta GetMinimumTimeInBackgroundForTesting() const;
+
  private:
   void OnHeartbeatCallback();
   void ScheduleNextHeartbeat(base::TimeDelta interval);
diff --git a/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy_unittest.cc b/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy_unittest.cc
index 5cb09c1..233f882 100644
--- a/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy_unittest.cc
+++ b/chrome/browser/performance_manager/policies/heuristic_memory_saver_policy_unittest.cc
@@ -89,24 +89,30 @@
           kDefaultHeartbeatInterval,
       base::TimeDelta minimum_time_in_background =
           kDefaultMinimumTimeInBackground,
+      bool feature_enabled = true,
       HeuristicMemorySaverPolicy::AvailableMemoryCallback
           available_memory_callback = base::BindRepeating([] {
             return kDefaultAvailableMemoryValue;
           }),
       HeuristicMemorySaverPolicy::TotalMemoryCallback total_memory_callback =
           base::BindRepeating([] { return kDefaultTotalMemoryValue; })) {
-    feature_list_.InitAndEnableFeatureWithParameters(
-        features::kHeuristicMemorySaver,
-        {
-            {"threshold_percent", base::NumberToString(pmf_threshold_percent)},
-            {"threshold_mb", base::NumberToString(pmf_threshold_mb)},
-            {"threshold_reached_heartbeat_interval",
-             FormatTimeDeltaParam(threshold_reached_heartbeat_interval)},
-            {"threshold_not_reached_heartbeat_interval",
-             FormatTimeDeltaParam(threshold_not_reached_heartbeat_interval)},
-            {"minimum_time_in_background",
-             FormatTimeDeltaParam(minimum_time_in_background)},
-        });
+    if (feature_enabled) {
+      feature_list_.InitAndEnableFeatureWithParameters(
+          features::kHeuristicMemorySaver,
+          {
+              {"threshold_percent",
+               base::NumberToString(pmf_threshold_percent)},
+              {"threshold_mb", base::NumberToString(pmf_threshold_mb)},
+              {"threshold_reached_heartbeat_interval",
+               FormatTimeDeltaParam(threshold_reached_heartbeat_interval)},
+              {"threshold_not_reached_heartbeat_interval",
+               FormatTimeDeltaParam(threshold_not_reached_heartbeat_interval)},
+              {"minimum_time_in_background",
+               FormatTimeDeltaParam(minimum_time_in_background)},
+          });
+    } else {
+      feature_list_.InitAndDisableFeature(features::kHeuristicMemorySaver);
+    }
 
     auto policy = std::make_unique<HeuristicMemorySaverPolicy>(
         available_memory_callback, total_memory_callback);
@@ -151,8 +157,9 @@
   // Advance the time by at least `minimum_time_in_background` +
   // `heartbeat_interval`. If a tab is to be discarded, it will be at this
   // point.
-  task_env().FastForwardBy(kDefaultHeartbeatInterval +
-                           kDefaultMinimumTimeInBackground);
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting() +
+      policy()->GetMinimumTimeInBackgroundForTesting());
   // No discard.
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
@@ -168,7 +175,7 @@
   page_node()->SetIsVisible(false);
 
   // Advance the time by at least `minimum_time_in_background`
-  task_env().FastForwardBy(kDefaultMinimumTimeInBackground);
+  task_env().FastForwardBy(policy()->GetMinimumTimeInBackgroundForTesting());
   // No discard yet.
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 
@@ -176,7 +183,8 @@
   // now-eligible tab.
   EXPECT_CALL(*discarder(), DiscardPageNodeImpl(page_node()))
       .WillOnce(Return(true));
-  task_env().FastForwardBy(kDefaultHeartbeatInterval);
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting());
 
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
@@ -194,8 +202,52 @@
   // Advance the time by at least `minimum_time_in_background` +
   // `heartbeat_interval`. If a tab is to be discarded, it will be at this
   // point.
-  task_env().FastForwardBy(kDefaultHeartbeatInterval +
-                           kDefaultMinimumTimeInBackground);
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting() +
+      policy()->GetMinimumTimeInBackgroundForTesting());
+  // No discard.
+  ::testing::Mock::VerifyAndClearExpectations(discarder());
+}
+
+TEST_F(HeuristicMemorySaverPolicyTest, NoDiscardWithZeroThresholdPercent) {
+  // Verify that a 0 threshold doesn't cause division by zero.
+  CreatePolicy(/*pmf_threshold_percent=*/0);
+  policy()->SetActive(true);
+
+  page_node()->SetType(PageType::kTab);
+  // Toggle visibility so that the page node updates its last visibility timing
+  // information.
+  page_node()->SetIsVisible(true);
+  page_node()->SetIsVisible(false);
+
+  // Advance the time by at least `minimum_time_in_background` +
+  // `heartbeat_interval`. If a tab is to be discarded, it will be at this
+  // point.
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting() +
+      policy()->GetMinimumTimeInBackgroundForTesting());
+  // No discard.
+  ::testing::Mock::VerifyAndClearExpectations(discarder());
+}
+
+TEST_F(HeuristicMemorySaverPolicyTest, NoDiscardWithZeroThresholdMB) {
+  // Verify that a 0 threshold doesn't cause division by zero.
+  CreatePolicy(/*pmf_threshold_percent=*/100,
+               /*pmf_threshold_mb=*/0);
+  policy()->SetActive(true);
+
+  page_node()->SetType(PageType::kTab);
+  // Toggle visibility so that the page node updates its last visibility timing
+  // information.
+  page_node()->SetIsVisible(true);
+  page_node()->SetIsVisible(false);
+
+  // Advance the time by at least `minimum_time_in_background` +
+  // `heartbeat_interval`. If a tab is to be discarded, it will be at this
+  // point.
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting() +
+      policy()->GetMinimumTimeInBackgroundForTesting());
   // No discard.
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
@@ -217,6 +269,7 @@
       /*threshold_reached_heartbeat_interval=*/kDefaultHeartbeatInterval,
       /*threshold_not_reached_heartbeat_interval=*/kDefaultHeartbeatInterval,
       /*minimum_time_in_background=*/kDefaultMinimumTimeInBackground,
+      /*feature_enabled=*/true,
       /*available_memory_callback=*/
       base::BindRepeating(&MemoryMetricsMocker::GetAvailableMemory,
                           base::Unretained(&mocker)),
@@ -233,7 +286,7 @@
   page_node()->SetIsVisible(false);
 
   // Advance the time by at least `minimum_time_in_background`
-  task_env().FastForwardBy(kDefaultMinimumTimeInBackground);
+  task_env().FastForwardBy(policy()->GetMinimumTimeInBackgroundForTesting());
   // No discard yet.
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 
@@ -241,7 +294,58 @@
   // now-eligible tab.
   EXPECT_CALL(*discarder(), DiscardPageNodeImpl(page_node()))
       .WillOnce(Return(true));
-  task_env().FastForwardBy(kDefaultHeartbeatInterval);
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting());
+
+  ::testing::Mock::VerifyAndClearExpectations(discarder());
+}
+
+TEST_F(HeuristicMemorySaverPolicyTest, DiscardIfPolicyActiveWithoutFeature) {
+  // Pretend there's 1 byte of memory available out of 8 GB. That should be low
+  // enough to trigger a discard no matter what the default thresholds are.
+  MemoryMetricsMocker mocker;
+  mocker.SetAvailableMemory(1);
+  mocker.SetTotalMemory(8 * kBytesPerGb);
+
+  // It should be possible to activate the policy even if the
+  // kHeuristicMemorySaver feature is disabled (it just won't happen by
+  // default). In this case the feature parameters are ignored.
+  CreatePolicy(
+      /*pmf_threshold_percent=*/0,
+      /*pmf_threshold_mb=*/0,
+      /*threshold_reached_heartbeat_interval=*/base::TimeDelta(),
+      /*threshold_not_reached_heartbeat_interval=*/base::TimeDelta(),
+      /*minimum_time_in_background=*/base::TimeDelta(),
+      /*feature_enabled=*/false,
+      /*available_memory_callback=*/
+      base::BindRepeating(&MemoryMetricsMocker::GetAvailableMemory,
+                          base::Unretained(&mocker)),
+      /*total_memory_callback=*/
+      base::BindRepeating(&MemoryMetricsMocker::GetTotalMemory,
+                          base::Unretained(&mocker)));
+
+  policy()->SetActive(true);
+
+  EXPECT_FALSE(
+      policy()->GetThresholdReachedHeartbeatIntervalForTesting().is_zero());
+  EXPECT_FALSE(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting().is_zero());
+  EXPECT_FALSE(policy()->GetMinimumTimeInBackgroundForTesting().is_zero());
+
+  page_node()->SetType(PageType::kTab);
+  // Toggle visibility so that the page node updates its last visibility timing
+  // information.
+  page_node()->SetIsVisible(true);
+  page_node()->SetIsVisible(false);
+
+  // Advance the time by at least `minimum_time_in_background` +
+  // `heartbeat_interval`. One or more tabs should be discarded by this point.
+  // The exact timing depends on the default values of the policy parameters.
+  EXPECT_CALL(*discarder(), DiscardPageNodeImpl(page_node()))
+      .WillRepeatedly(Return(true));
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting() +
+      policy()->GetMinimumTimeInBackgroundForTesting());
 
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
@@ -264,6 +368,7 @@
       /*threshold_reached_heartbeat_interval=*/kDefaultHeartbeatInterval,
       /*threshold_not_reached_heartbeat_interval=*/kDefaultHeartbeatInterval,
       /*minimum_time_in_background=*/kDefaultMinimumTimeInBackground,
+      /*feature_enabled=*/true,
       /*available_memory_callback=*/
       base::BindRepeating(&MemoryMetricsMocker::GetAvailableMemory,
                           base::Unretained(&mocker)),
@@ -282,8 +387,9 @@
   // Advance the time by at least `minimum_time_in_background` +
   // `heartbeat_interval`. If a tab is to be discarded, it will be at this
   // point.
-  task_env().FastForwardBy(kDefaultHeartbeatInterval +
-                           kDefaultMinimumTimeInBackground);
+  task_env().FastForwardBy(
+      policy()->GetThresholdNotReachedHeartbeatIntervalForTesting() +
+      policy()->GetMinimumTimeInBackgroundForTesting());
   // No discard.
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
@@ -299,6 +405,7 @@
       /*threshold_reached_heartbeat_interval=*/kDefaultHeartbeatInterval,
       /*threshold_not_reached_heartbeat_interval=*/kLongHeartbeatInterval,
       /*minimum_time_in_background=*/kDefaultMinimumTimeInBackground,
+      /*feature_enabled=*/true,
       /*available_memory_callback=*/
       base::BindRepeating(&MemoryMetricsMocker::GetAvailableMemory,
                           base::Unretained(&mocker)),
@@ -308,6 +415,11 @@
 
   policy()->SetActive(true);
 
+  EXPECT_EQ(policy()->GetThresholdNotReachedHeartbeatIntervalForTesting(),
+            kLongHeartbeatInterval);
+  EXPECT_EQ(policy()->GetThresholdReachedHeartbeatIntervalForTesting(),
+            kDefaultHeartbeatInterval);
+
   // Advance the time just enough to get the first heartbeat
   task_env().FastForwardBy(kDefaultHeartbeatInterval);
   // No discard yet.
diff --git a/chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h b/chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h
index abbd9bc..e4f4fda 100644
--- a/chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h
+++ b/chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/performance_manager/user_tuning/user_performance_tuning_notifier.h"
 #include "chrome/browser/resource_coordinator/lifecycle_unit_state.mojom-shared.h"
+#include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "content/public/browser/web_contents_user_data.h"
 
@@ -52,7 +53,8 @@
 
   class HighEfficiencyModeDelegate {
    public:
-    virtual void ToggleHighEfficiencyMode(bool enabled) = 0;
+    virtual void ToggleHighEfficiencyMode(
+        prefs::HighEfficiencyModeState state) = 0;
     virtual void SetTimeBeforeDiscard(base::TimeDelta time_before_discard) = 0;
     virtual ~HighEfficiencyModeDelegate() = default;
   };
diff --git a/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.cc b/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.cc
index c10b9377..feeefc4 100644
--- a/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.cc
+++ b/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.cc
@@ -4,18 +4,30 @@
 
 #include "chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.h"
 
-namespace performance_manager {
+namespace performance_manager::user_tuning {
 
-void FakeHighEfficiencyModeDelegate::ToggleHighEfficiencyMode(bool enabled) {}
+void FakeHighEfficiencyModeDelegate::ToggleHighEfficiencyMode(
+    prefs::HighEfficiencyModeState state) {
+  last_state_ = state;
+}
 
 void FakeHighEfficiencyModeDelegate::SetTimeBeforeDiscard(
     base::TimeDelta time_before_discard) {
-  FakeHighEfficiencyModeDelegate::last_time_before_discard =
-      time_before_discard;
+  last_time_before_discard_ = time_before_discard;
 }
 
-base::TimeDelta FakeHighEfficiencyModeDelegate::GetLastTimeBeforeDiscard() {
-  return last_time_before_discard;
+void FakeHighEfficiencyModeDelegate::ClearLastState() {
+  last_state_.reset();
 }
 
-}  // namespace performance_manager
+absl::optional<prefs::HighEfficiencyModeState>
+FakeHighEfficiencyModeDelegate::GetLastState() const {
+  return last_state_;
+}
+
+absl::optional<base::TimeDelta>
+FakeHighEfficiencyModeDelegate::GetLastTimeBeforeDiscard() const {
+  return last_time_before_discard_;
+}
+
+}  // namespace performance_manager::user_tuning
diff --git a/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.h b/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.h
index 7e47d7cc..7bff447 100644
--- a/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.h
+++ b/chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.h
@@ -7,24 +7,30 @@
 
 #include "base/time/time.h"
 #include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
+#include "components/performance_manager/public/user_tuning/prefs.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace performance_manager {
+namespace performance_manager::user_tuning {
 
 class FakeHighEfficiencyModeDelegate
-    : public performance_manager::user_tuning::UserPerformanceTuningManager::
-          HighEfficiencyModeDelegate {
+    : public UserPerformanceTuningManager::HighEfficiencyModeDelegate {
  public:
   // overrides of methods in HighEfficiencyModeDelegate
-  void ToggleHighEfficiencyMode(bool enabled) override;
+  void ToggleHighEfficiencyMode(prefs::HighEfficiencyModeState state) override;
   void SetTimeBeforeDiscard(base::TimeDelta time_before_discard) override;
   ~FakeHighEfficiencyModeDelegate() override = default;
 
-  base::TimeDelta GetLastTimeBeforeDiscard();
+  void ClearLastState();
+
+  absl::optional<prefs::HighEfficiencyModeState> GetLastState() const;
+
+  absl::optional<base::TimeDelta> GetLastTimeBeforeDiscard() const;
 
  private:
-  base::TimeDelta last_time_before_discard = base::TimeDelta::Max();
+  absl::optional<base::TimeDelta> last_time_before_discard_;
+  absl::optional<prefs::HighEfficiencyModeState> last_state_;
 };
 
-}  // namespace performance_manager
+}  // namespace performance_manager::user_tuning
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_TEST_SUPPORT_FAKE_HIGH_EFFICIENCY_MODE_DELEGATE_H_
diff --git a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc
index aa61112..ada1944 100644
--- a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc
+++ b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc
@@ -8,6 +8,7 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
+#include "base/notreached.h"
 #include "base/power_monitor/battery_state_sampler.h"
 #include "base/power_monitor/power_monitor.h"
 #include "base/power_monitor/power_observer.h"
@@ -97,39 +98,49 @@
     : public performance_manager::user_tuning::UserPerformanceTuningManager::
           HighEfficiencyModeDelegate {
  public:
-  void ToggleHighEfficiencyMode(bool enabled) override {
+  void ToggleHighEfficiencyMode(HighEfficiencyModeState state) override {
     performance_manager::PerformanceManager::CallOnGraph(
         FROM_HERE,
         base::BindOnce(
-            [](bool enabled, performance_manager::Graph* graph) {
-              if (base::FeatureList::IsEnabled(
-                      performance_manager::features::kHeuristicMemorySaver)) {
-                CHECK(policies::HeuristicMemorySaverPolicy::GetInstance());
-                policies::HeuristicMemorySaverPolicy::GetInstance()->SetActive(
-                    enabled);
-              } else {
-                CHECK(policies::HighEfficiencyModePolicy::GetInstance());
-                policies::HighEfficiencyModePolicy::GetInstance()
-                    ->OnHighEfficiencyModeChanged(enabled);
+            [](HighEfficiencyModeState state) {
+              auto* heuristic_memory_saver_policy =
+                  policies::HeuristicMemorySaverPolicy::GetInstance();
+              CHECK(heuristic_memory_saver_policy);
+              auto* high_efficiency_mode_policy =
+                  policies::HighEfficiencyModePolicy::GetInstance();
+              CHECK(high_efficiency_mode_policy);
+              switch (state) {
+                case HighEfficiencyModeState::kDisabled:
+                  heuristic_memory_saver_policy->SetActive(false);
+                  high_efficiency_mode_policy->OnHighEfficiencyModeChanged(
+                      false);
+                  return;
+                case HighEfficiencyModeState::kEnabled:
+                  heuristic_memory_saver_policy->SetActive(true);
+                  high_efficiency_mode_policy->OnHighEfficiencyModeChanged(
+                      false);
+                  return;
+                case HighEfficiencyModeState::kEnabledOnTimer:
+                  heuristic_memory_saver_policy->SetActive(false);
+                  high_efficiency_mode_policy->OnHighEfficiencyModeChanged(
+                      true);
+                  return;
               }
+              NOTREACHED_NORETURN();
             },
-            enabled));
+            state));
   }
 
   void SetTimeBeforeDiscard(base::TimeDelta time_before_discard) override {
     performance_manager::PerformanceManager::CallOnGraph(
-        FROM_HERE,
-        base::BindOnce(
-            [](base::TimeDelta time_before_discard,
-               performance_manager::Graph* graph) {
-              if (!base::FeatureList::IsEnabled(
-                      performance_manager::features::kHeuristicMemorySaver)) {
-                CHECK(policies::HighEfficiencyModePolicy::GetInstance());
-                policies::HighEfficiencyModePolicy::GetInstance()
-                    ->SetTimeBeforeDiscard(time_before_discard);
-              }
-            },
-            time_before_discard));
+        FROM_HERE, base::BindOnce(
+                       [](base::TimeDelta time_before_discard) {
+                         auto* policy =
+                             policies::HighEfficiencyModePolicy::GetInstance();
+                         CHECK(policy);
+                         policy->SetTimeBeforeDiscard(time_before_discard);
+                       },
+                       time_before_discard));
   }
 
   ~HighEfficiencyModeDelegateImpl() override = default;
@@ -677,9 +688,24 @@
 }
 
 void UserPerformanceTuningManager::OnHighEfficiencyModePrefChanged() {
-  high_efficiency_mode_delegate_->ToggleHighEfficiencyMode(
-      IsHighEfficiencyModeActive());
-
+  HighEfficiencyModeState state =
+      prefs::GetCurrentHighEfficiencyModeState(pref_change_registrar_.prefs());
+  if (!IsHighEfficiencyModeManaged() &&
+      base::FeatureList::IsEnabled(features::kForceHeuristicMemorySaver)) {
+    // Set the heuristic policy for experimentation regardless of the pref.
+    state = base::FeatureList::IsEnabled(features::kHeuristicMemorySaver)
+                ? HighEfficiencyModeState::kEnabled
+                : HighEfficiencyModeState::kDisabled;
+  } else if (state != HighEfficiencyModeState::kDisabled &&
+             !base::FeatureList::IsEnabled(
+                 features::kHighEfficiencyMultistateMode)) {
+    // The user has enabled high efficiency mode, but without the multistate UI
+    // they didn't choose a policy. The feature controls which policy to use.
+    state = base::FeatureList::IsEnabled(features::kHeuristicMemorySaver)
+                ? HighEfficiencyModeState::kEnabled
+                : HighEfficiencyModeState::kEnabledOnTimer;
+  }
+  high_efficiency_mode_delegate_->ToggleHighEfficiencyMode(state);
   for (auto& obs : observers_) {
     obs.OnHighEfficiencyModeChanged();
   }
diff --git a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc
index 5fc0d4fb..60e010d 100644
--- a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc
+++ b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc
@@ -4,6 +4,10 @@
 
 #include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
 
+#include <tuple>
+#include <utility>
+#include <vector>
+
 #include "base/memory/raw_ptr.h"
 #include "base/power_monitor/battery_state_sampler.h"
 #include "base/power_monitor/power_monitor.h"
@@ -12,6 +16,8 @@
 #include "base/test/power_monitor_test_utils.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "base/values.h"
 #include "chrome/browser/performance_manager/test_support/fake_frame_throttling_delegate.h"
 #include "chrome/browser/performance_manager/test_support/fake_high_efficiency_mode_delegate.h"
 #include "chrome/browser/performance_manager/test_support/fake_power_monitor_source.h"
@@ -20,6 +26,7 @@
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chromeos/dbus/power/power_manager_client.h"
@@ -31,6 +38,12 @@
 namespace performance_manager::user_tuning {
 namespace {
 
+using HighEfficiencyModeState = prefs::HighEfficiencyModeState;
+using ::testing::Bool;
+using ::testing::Combine;
+using ::testing::Optional;
+using ::testing::ValuesIn;
+
 class QuitRunLoopObserverBase : public performance_manager::user_tuning::
                                     UserPerformanceTuningManager::Observer {
  public:
@@ -101,7 +114,7 @@
 
 }  // namespace
 
-class UserPerformanceTuningManagerTest : public testing::Test {
+class UserPerformanceTuningManagerTest : public ::testing::Test {
  public:
   void SetUp() override {
     auto source = std::make_unique<FakePowerMonitorSource>();
@@ -286,8 +299,8 @@
   EXPECT_EQ(5, local_state_.GetInteger(
                    performance_manager::user_tuning::prefs::
                        kHighEfficiencyModeTimeBeforeDiscardInMinutes));
-  EXPECT_EQ(base::Minutes(5),
-            high_efficiency_mode_delegate_->GetLastTimeBeforeDiscard());
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastTimeBeforeDiscard(),
+              Optional(base::Minutes(5)));
 }
 
 TEST_F(UserPerformanceTuningManagerTest, EnabledOnBatteryPower) {
@@ -513,8 +526,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(UserPerformanceTuningManagerTest, ManagedFromPowerManager) {
-  base::test::ScopedFeatureList feature_list_;
-  feature_list_.InitAndEnableFeature(
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
       performance_manager::features::kUseDeviceBatterySaverChromeOS);
 
   StartManager();
@@ -540,8 +553,8 @@
 
 TEST_F(UserPerformanceTuningManagerTest,
        StartsEnabledIfAlreadyEnabledInPowerManager) {
-  base::test::ScopedFeatureList feature_list_;
-  feature_list_.InitAndEnableFeature(
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
       performance_manager::features::kUseDeviceBatterySaverChromeOS);
 
   // Request to enable PowerManager's BSM
@@ -566,4 +579,168 @@
 }
 #endif
 
+// States of the HeuristicMemorySaver and MultistateMode features to test, with
+// expected outcomes.
+struct PrefTestFeatureState {
+  // Whether the MultistateMode feature is enabled.
+  bool is_multistate_enabled;
+
+  // Whether the HeuristicMemorySaver feature is enabled.
+  bool is_heuristic_memory_saver_enabled;
+
+  // The expected result when the pref is set to kEnabled with these params.
+  HighEfficiencyModeState expected_enabled_state;
+
+  // The expected result when the pref is set to kEnabledOnTimer with these
+  // params.
+  HighEfficiencyModeState expected_enabled_on_timer_state;
+};
+
+// List of feature states to test, not including the ForceHeuristicMemorySaver
+// feature. These include the expected states that should be passed to
+// ToggleHighEfficiencyMode when ForceHeuristicMemorySaver is disabled.
+constexpr PrefTestFeatureState kNonForcedPrefTestFeatureStates[] = {
+    // If multistate is off, both "enabled" states are controlled by the
+    // HeuristicMemorySaver feature.
+    {
+        .is_multistate_enabled = false,
+        .is_heuristic_memory_saver_enabled = false,
+        .expected_enabled_state = HighEfficiencyModeState::kEnabledOnTimer,
+        .expected_enabled_on_timer_state =
+            HighEfficiencyModeState::kEnabledOnTimer,
+    },
+    {
+        .is_multistate_enabled = false,
+        .is_heuristic_memory_saver_enabled = true,
+        .expected_enabled_state = HighEfficiencyModeState::kEnabled,
+        .expected_enabled_on_timer_state = HighEfficiencyModeState::kEnabled,
+    },
+    // If multistate is on, the true "enabled" state is used regardless of the
+    // HeuristicMemorySaver feature.
+    {
+        .is_multistate_enabled = true,
+        .is_heuristic_memory_saver_enabled = false,
+        .expected_enabled_state = HighEfficiencyModeState::kEnabled,
+        .expected_enabled_on_timer_state =
+            HighEfficiencyModeState::kEnabledOnTimer,
+    },
+    {
+        .is_multistate_enabled = true,
+        .is_heuristic_memory_saver_enabled = true,
+        .expected_enabled_state = HighEfficiencyModeState::kEnabled,
+        .expected_enabled_on_timer_state =
+            HighEfficiencyModeState::kEnabledOnTimer,
+    }};
+
+// Test parameters are (feature_state, is_force_heuristic_memory_saver_enabled).
+// Each feature state above is tested once with the ForceHeuristicMemorySaver
+// feature enabled and once with it disabled.
+class UserPerformanceTuningManagerPrefTest
+    : public UserPerformanceTuningManagerTest,
+      public ::testing::WithParamInterface<
+          std::tuple<PrefTestFeatureState, bool>> {
+ protected:
+  void SetUp() override {
+    UserPerformanceTuningManagerTest::SetUp();
+
+    std::tie(feature_state_, is_force_heuristic_memory_saver_enabled_) =
+        GetParam();
+
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+    if (feature_state_.is_multistate_enabled) {
+      enabled_features.push_back(features::kHighEfficiencyMultistateMode);
+    } else {
+      disabled_features.push_back(features::kHighEfficiencyMultistateMode);
+    }
+    if (feature_state_.is_heuristic_memory_saver_enabled) {
+      enabled_features.push_back(features::kHeuristicMemorySaver);
+    } else {
+      disabled_features.push_back(features::kHeuristicMemorySaver);
+    }
+    if (is_force_heuristic_memory_saver_enabled_) {
+      enabled_features.push_back(features::kForceHeuristicMemorySaver);
+    } else {
+      disabled_features.push_back(features::kForceHeuristicMemorySaver);
+    }
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+  base::Value ValueForState(HighEfficiencyModeState state) {
+    return base::Value(static_cast<int>(state));
+  }
+
+  PrefTestFeatureState feature_state_;
+  bool is_force_heuristic_memory_saver_enabled_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         UserPerformanceTuningManagerPrefTest,
+                         Combine(ValuesIn(kNonForcedPrefTestFeatureStates),
+                                 Bool()));
+
+TEST_P(UserPerformanceTuningManagerPrefTest, OnHighEfficiencyModePrefChanged) {
+  StartManager();
+
+  // If the ForceHeuristicMemorySaver feature is overriding the pref, the final
+  // state should always be based on the HeuristicMemorySaver feature.
+  absl::optional<HighEfficiencyModeState> expected_overridden_state;
+  if (is_force_heuristic_memory_saver_enabled_) {
+    expected_overridden_state = feature_state_.is_heuristic_memory_saver_enabled
+                                    ? HighEfficiencyModeState::kEnabled
+                                    : HighEfficiencyModeState::kDisabled;
+  }
+
+  local_state_.SetUserPref(prefs::kHighEfficiencyModeState,
+                           ValueForState(HighEfficiencyModeState::kDisabled));
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastState(),
+              Optional(expected_overridden_state.value_or(
+                  HighEfficiencyModeState::kDisabled)));
+
+  high_efficiency_mode_delegate_->ClearLastState();
+
+  local_state_.SetUserPref(prefs::kHighEfficiencyModeState,
+                           ValueForState(HighEfficiencyModeState::kEnabled));
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastState(),
+              Optional(expected_overridden_state.value_or(
+                  feature_state_.expected_enabled_state)));
+  high_efficiency_mode_delegate_->ClearLastState();
+
+  local_state_.SetUserPref(
+      prefs::kHighEfficiencyModeState,
+      ValueForState(HighEfficiencyModeState::kEnabledOnTimer));
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastState(),
+              Optional(expected_overridden_state.value_or(
+                  feature_state_.expected_enabled_on_timer_state)));
+  high_efficiency_mode_delegate_->ClearLastState();
+}
+
+TEST_P(UserPerformanceTuningManagerPrefTest,
+       OnHighEfficiencyModePrefChangedManaged) {
+  StartManager();
+
+  // Since the pref is managed, ForceHeuristicMemorySaver is not allowed to
+  // override it, so ignore `is_force_heuristic_memory_saver_enabled_`.
+  local_state_.SetManagedPref(
+      prefs::kHighEfficiencyModeState,
+      ValueForState(HighEfficiencyModeState::kDisabled));
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastState(),
+              Optional(HighEfficiencyModeState::kDisabled));
+  high_efficiency_mode_delegate_->ClearLastState();
+
+  local_state_.SetManagedPref(prefs::kHighEfficiencyModeState,
+                              ValueForState(HighEfficiencyModeState::kEnabled));
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastState(),
+              Optional(feature_state_.expected_enabled_state));
+  high_efficiency_mode_delegate_->ClearLastState();
+
+  local_state_.SetManagedPref(
+      prefs::kHighEfficiencyModeState,
+      ValueForState(HighEfficiencyModeState::kEnabledOnTimer));
+  EXPECT_THAT(high_efficiency_mode_delegate_->GetLastState(),
+              Optional(feature_state_.expected_enabled_on_timer_state));
+  high_efficiency_mode_delegate_->ClearLastState();
+}
+
 }  // namespace performance_manager::user_tuning
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector.h b/chrome/browser/permissions/prediction_based_permission_ui_selector.h
index 3e4f2dad..28bf4ee 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector.h
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "components/permissions/permission_actions_history.h"
 #include "components/permissions/permission_ui_selector.h"
diff --git a/chrome/browser/policy/extension_force_install_mixin.cc b/chrome/browser/policy/extension_force_install_mixin.cc
index ea1246d..13ade1c 100644
--- a/chrome/browser/policy/extension_force_install_mixin.cc
+++ b/chrome/browser/policy/extension_force_install_mixin.cc
@@ -63,8 +63,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "components/policy/proto/chrome_device_policy.pb.h"
 #endif
 
diff --git a/chrome/browser/policy/restricted_mgs_policy_provider_ash_browsertest.cc b/chrome/browser/policy/restricted_mgs_policy_provider_ash_browsertest.cc
index 292734b..406e75d 100644
--- a/chrome/browser/policy/restricted_mgs_policy_provider_ash_browsertest.cc
+++ b/chrome/browser/policy/restricted_mgs_policy_provider_ash_browsertest.cc
@@ -4,9 +4,9 @@
 
 #include "ash/constants/ash_switches.h"
 #include "base/values.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/prefs/incognito_mode_prefs.cc b/chrome/browser/prefs/incognito_mode_prefs.cc
index 32906fa1..d84951d 100644
--- a/chrome/browser/prefs/incognito_mode_prefs.cc
+++ b/chrome/browser/prefs/incognito_mode_prefs.cc
@@ -165,12 +165,6 @@
       forced_by_switch ||
       GetAvailabilityInternal(prefs, DONT_CHECK_PARENTAL_CONTROLS) ==
           IncognitoModeAvailability::kForced;
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  auto* init_params = chromeos::BrowserParamsProxy::Get();
-  should_use_incognito |=
-      init_params->InitialBrowserAction() ==
-      crosapi::mojom::InitialBrowserAction::kOpenIncognitoWindow;
-#endif
   return should_use_incognito &&
          GetAvailabilityInternal(prefs, CHECK_PARENTAL_CONTROLS) !=
              IncognitoModeAvailability::kDisabled;
diff --git a/chrome/browser/printing/print_backend_service_manager.h b/chrome/browser/printing/print_backend_service_manager.h
index 0b68179..41788f42 100644
--- a/chrome/browser/printing/print_backend_service_manager.h
+++ b/chrome/browser/printing/print_backend_service_manager.h
@@ -11,6 +11,7 @@
 
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/no_destructor.h"
 #include "base/types/strong_alias.h"
diff --git a/chrome/browser/privacy/privacy_metrics_service.h b/chrome/browser/privacy/privacy_metrics_service.h
index d202418c..5b7e6c4 100644
--- a/chrome/browser/privacy/privacy_metrics_service.h
+++ b/chrome/browser/privacy/privacy_metrics_service.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_PRIVACY_PRIVACY_METRICS_SERVICE_H_
 #define CHROME_BROWSER_PRIVACY_PRIVACY_METRICS_SERVICE_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
diff --git a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_history_sync_step.xml b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_history_sync_step.xml
index a2d110c..c0a9255b 100644
--- a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_history_sync_step.xml
+++ b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_history_sync_step.xml
@@ -35,7 +35,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:showText="false"
-            android:text="@string/privacy_guide_history_sync_toggle" />
+            android:text="@string/privacy_guide_history_sync_toggle"
+            android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
 
         <org.chromium.chrome.browser.privacy_guide.PrivacyGuideExplanationHeading
             android:layout_width="match_parent"
diff --git a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_msbb_step.xml b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_msbb_step.xml
index d789f774..4e6a78b 100644
--- a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_msbb_step.xml
+++ b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_msbb_step.xml
@@ -35,7 +35,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:showText="false"
-            android:text="@string/url_keyed_anonymized_data_title" />
+            android:text="@string/url_keyed_anonymized_data_title"
+            android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
 
         <org.chromium.chrome.browser.privacy_guide.PrivacyGuideExplanationHeading
             android:layout_width="match_parent"
diff --git a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_welcome.xml b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_welcome.xml
index 235b6aa..a94a204 100644
--- a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_welcome.xml
+++ b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_welcome.xml
@@ -33,7 +33,7 @@
             android:layout_height="wrap_content"
             android:layout_marginVertical="16dp"
             android:text="@string/privacy_guide_welcome_title"
-            style="@style/TextAppearance.Headline.Primary" />
+            style="@style/TextAppearance.Headline2Thick.Primary" />
 
         <TextView
             android:layout_width="wrap_content"
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
index 15650c2..eb1c2a5 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
@@ -1429,6 +1429,8 @@
         pref_service->SetInteger(
             prefs::kPrivacySandboxM1PromptSuppressed,
             static_cast<int>(PromptSuppressedReason::kNoticeShownToGuardian));
+        pref_service->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled,
+                                 true);
         return PromptType::kNone;
       }
     }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
index ed62eadc..04e2bef 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
@@ -4505,6 +4505,23 @@
   }
 }
 
+TEST_F(PrivacySandboxServiceM1RestrictedNoticePromptTest,
+       RestrictedNoticeAcknowledged) {
+  // Ensure that Ad measurement pref is not re-enabled if user disabled it
+  // after acknowledging the restricted notice.
+  RunTestCase(
+      TestState{{StateKey::kM1PromptSuppressedReason,
+                 static_cast<int>(PromptSuppressedReason::kNone)},
+                {StateKey::kM1RestrictedNoticeAcknowledged, true},
+                {StateKey::kM1AdMeasurementEnabledUserPrefValue, false}},
+      TestInput{{InputKey::kForceChromeBuild, true}},
+      TestOutput{{OutputKey::kPromptType,
+                  static_cast<int>(PrivacySandboxService::PromptType::kNone)},
+                 {OutputKey::kM1AdMeasurementEnabled, false},
+                 {OutputKey::kM1PromptSuppressedReason,
+                  static_cast<int>(PromptSuppressedReason::kNone)}});
+}
+
 class PrivacySandboxServiceM1RestrictedNoticeShownToGuardianTest
     : public PrivacySandboxServiceM1PromptTest {
  public:
@@ -4530,6 +4547,7 @@
        NotSubjectToNoticeButIsRestricted) {
   // Ensure that kNoticeShownToGuardian, with no prompt, is returned in the
   // event that the user is not subject to the m1 notice restricted prompt.
+  // Ensure measurements API is enabled for these users.
   RunTestCase(
       TestState{{StateKey::kM1PromptSuppressedReason,
                  static_cast<int>(PromptSuppressedReason::kNone)},
@@ -4538,6 +4556,25 @@
       TestOutput{
           {OutputKey::kPromptType, static_cast<int>(PromptType::kNone)},
           {OutputKey::kM1PromptSuppressedReason,
+           static_cast<int>(PromptSuppressedReason::kNoticeShownToGuardian)},
+          {OutputKey::kM1AdMeasurementEnabled, true}});
+}
+
+TEST_F(PrivacySandboxServiceM1RestrictedNoticeShownToGuardianTest,
+       NotSubjectToNoticeButIsRestrictedWithAdMeasurementDisabled) {
+  // Ensure that Ad measurement pref is not re-enabled if user disabled it
+  // after the notice was suppressed due to kNoticeShownToGuardian.
+  RunTestCase(
+      TestState{
+          {StateKey::kM1PromptSuppressedReason,
+           static_cast<int>(PromptSuppressedReason::kNoticeShownToGuardian)},
+          {StateKey::kM1AdMeasurementEnabledUserPrefValue, false}},
+      TestInput{{InputKey::kForceChromeBuild, true}},
+      TestOutput{
+          {OutputKey::kPromptType,
+           static_cast<int>(PrivacySandboxService::PromptType::kNone)},
+          {OutputKey::kM1AdMeasurementEnabled, false},
+          {OutputKey::kM1PromptSuppressedReason,
            static_cast<int>(PromptSuppressedReason::kNoticeShownToGuardian)}});
 }
 
diff --git a/chrome/browser/recent_tabs/BUILD.gn b/chrome/browser/recent_tabs/BUILD.gn
index ee0db16..bcec0a47 100644
--- a/chrome/browser/recent_tabs/BUILD.gn
+++ b/chrome/browser/recent_tabs/BUILD.gn
@@ -17,6 +17,7 @@
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/tab:java",
     "//chrome/browser/tabmodel:java",
+    "//components/sync_device_info:sync_device_info_java",
     "//content/public/android:content_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//url:gurl_java",
diff --git a/chrome/browser/recent_tabs/android/java/src/org/chromium/chrome/browser/recent_tabs/ForeignSessionHelper.java b/chrome/browser/recent_tabs/android/java/src/org/chromium/chrome/browser/recent_tabs/ForeignSessionHelper.java
index 98e7e7e..950987b 100644
--- a/chrome/browser/recent_tabs/android/java/src/org/chromium/chrome/browser/recent_tabs/ForeignSessionHelper.java
+++ b/chrome/browser/recent_tabs/android/java/src/org/chromium/chrome/browser/recent_tabs/ForeignSessionHelper.java
@@ -13,6 +13,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.components.sync_device_info.FormFactor;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.url.GURL;
@@ -44,24 +45,29 @@
 
     /**
      * Represents synced foreign session.
+     * Form factor value correlations can be found in the components/sync_device_info/device_info.h
+     * file or its Java generated counterpart.
      */
     public static class ForeignSession {
         public final String tag;
         public final String name;
         public final long modifiedTime;
         public final List<ForeignSessionWindow> windows = new ArrayList<ForeignSessionWindow>();
+        public final @FormFactor int formFactor;
 
-        private ForeignSession(String tag, String name, long modifiedTime) {
-            this(tag, name, modifiedTime, new ArrayList<>());
+        private ForeignSession(
+                String tag, String name, long modifiedTime, @FormFactor int formFactor) {
+            this(tag, name, modifiedTime, new ArrayList<>(), formFactor);
         }
 
         @VisibleForTesting
-        public ForeignSession(
-                String tag, String name, long modifiedTime, List<ForeignSessionWindow> windows) {
+        public ForeignSession(String tag, String name, long modifiedTime,
+                List<ForeignSessionWindow> windows, @FormFactor int formFactor) {
             this.tag = tag;
             this.name = name;
             this.modifiedTime = modifiedTime;
             this.windows.addAll(windows);
+            this.formFactor = formFactor;
         }
     }
 
@@ -105,9 +111,9 @@
     }
 
     @CalledByNative
-    private static ForeignSession pushSession(
-            List<ForeignSession> sessions, String tag, String name, long modifiedTime) {
-        ForeignSession session = new ForeignSession(tag, name, modifiedTime);
+    private static ForeignSession pushSession(List<ForeignSession> sessions, String tag,
+            String name, long modifiedTime, @FormFactor int formFactor) {
+        ForeignSession session = new ForeignSession(tag, name, modifiedTime, formFactor);
         sessions.add(session);
         return session;
     }
diff --git a/chrome/browser/recent_tabs/internal/BUILD.gn b/chrome/browser/recent_tabs/internal/BUILD.gn
index 361e3f4..ec9e00b 100644
--- a/chrome/browser/recent_tabs/internal/BUILD.gn
+++ b/chrome/browser/recent_tabs/internal/BUILD.gn
@@ -23,9 +23,13 @@
     "android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsPromoSheetContent.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsProperties.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemProperties.java",
+    "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinder.java",
+    "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailItemDecoration.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenCoordinator.java",
+    "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinder.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenCoordinator.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinder.java",
+    "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsViewBinderHelper.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/TabItemProperties.java",
   ]
 
@@ -39,7 +43,10 @@
     "//chrome/browser/tabmodel:java",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/feature_engagement/public:public_java",
+    "//components/sync_device_info:sync_device_info_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
+    "//third_party/androidx:androidx_recyclerview_recyclerview_java",
     "//ui/android:ui_java",
   ]
   resources_package = "org.chromium.chrome.browser.recent_tabs"
@@ -54,11 +61,17 @@
   sources = [
     "android/java/res/anim/restore_tabs_view_flipper_fade_in.xml",
     "android/java/res/anim/restore_tabs_view_flipper_fade_out.xml",
-    "android/java/res/drawable/restore_tabs_device_icon.xml",
+    "android/java/res/drawable/restore_tabs_detail_item_background_bottom.xml",
+    "android/java/res/drawable/restore_tabs_detail_item_background_middle.xml",
+    "android/java/res/drawable/restore_tabs_detail_item_background_top.xml",
     "android/java/res/drawable/restore_tabs_device_info_background.xml",
     "android/java/res/drawable/restore_tabs_expand_more.xml",
     "android/java/res/drawable/restore_tabs_fre_logo.xml",
+    "android/java/res/drawable/restore_tabs_phone_icon.xml",
+    "android/java/res/drawable/restore_tabs_tablet_icon.xml",
     "android/java/res/layout/restore_tabs_bottom_sheet.xml",
+    "android/java/res/layout/restore_tabs_detail_screen_sheet.xml",
+    "android/java/res/layout/restore_tabs_foreign_session_item.xml",
     "android/java/res/layout/restore_tabs_promo_screen_sheet.xml",
     "android/java/res/values/dimens.xml",
   ]
@@ -84,6 +97,8 @@
     "android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediatorUnitTest.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsPromoSheetContentUnitTest.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemPropertiesUnitTest.java",
+    "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinderUnitTest.java",
+    "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinderUnitTest.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinderUnitTest.java",
     "android/java/src/org/chromium/chrome/browser/recent_tabs/ui/TabItemPropertiesUnitTest.java",
   ]
@@ -99,6 +114,7 @@
     "//chrome/browser/tabmodel:java",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/feature_engagement/public:public_java",
+    "//components/sync_device_info:sync_device_info_java",
     "//third_party/android_deps:espresso_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/hamcrest:hamcrest_library_java",
@@ -108,3 +124,28 @@
     "//url:gurl_junit_test_support",
   ]
 }
+
+android_library("unit_device_javatests") {
+  testonly = true
+
+  sources = [ "android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsUiRenderTest.java" ]
+
+  deps = [
+    ":java",
+    "//base:base_java_test_support",
+    "//chrome/browser/profiles/android:java",
+    "//chrome/browser/recent_tabs:java",
+    "//chrome/test/android:chrome_java_test_support_common",
+    "//components/browser_ui/bottomsheet/android:java",
+    "//components/sync_device_info:sync_device_info_java",
+    "//content/public/test/android:content_java_test_support",
+    "//third_party/android_deps:espresso_java",
+    "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
+    "//third_party/androidx:androidx_test_runner_java",
+    "//third_party/junit:junit",
+    "//third_party/mockito:mockito_java",
+    "//ui/android:ui_full_java",
+    "//ui/android:ui_java_test_support",
+    "//url:gurl_junit_test_support",
+  ]
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_bottom.xml b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_bottom.xml
new file mode 100644
index 0000000..3278d84
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_bottom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<org.chromium.chrome.browser.ui.fast_checkout.UnsharableRippleDrawable
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  android:color="?android:attr/colorControlHighlight">
+  <item>
+    <org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+      android:shape="rectangle"
+      app:surfaceElevation="@dimen/default_elevation_1">
+      <corners android:topLeftRadius="@dimen/restore_tabs_inner_corner_radius"
+        android:topRightRadius="@dimen/restore_tabs_inner_corner_radius"
+        android:bottomLeftRadius="@dimen/restore_tabs_outer_corner_radius"
+        android:bottomRightRadius="@dimen/restore_tabs_outer_corner_radius" />
+    </org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
+  </item>
+</org.chromium.chrome.browser.ui.fast_checkout.UnsharableRippleDrawable>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_middle.xml b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_middle.xml
new file mode 100644
index 0000000..a1376e0a
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_middle.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<org.chromium.chrome.browser.ui.fast_checkout.UnsharableRippleDrawable
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  android:color="?android:attr/colorControlHighlight">
+  <item>
+    <org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+      android:shape="rectangle"
+      app:surfaceElevation="@dimen/default_elevation_1">
+      <corners android:radius="@dimen/restore_tabs_inner_corner_radius" />
+    </org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
+  </item>
+</org.chromium.chrome.browser.ui.fast_checkout.UnsharableRippleDrawable>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_top.xml b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_top.xml
new file mode 100644
index 0000000..341cb9a
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_detail_item_background_top.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<org.chromium.chrome.browser.ui.fast_checkout.UnsharableRippleDrawable
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  android:color="?android:attr/colorControlHighlight">
+  <item>
+    <org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+      android:shape="rectangle"
+      app:surfaceElevation="@dimen/default_elevation_1">
+      <corners android:topLeftRadius="@dimen/restore_tabs_outer_corner_radius"
+        android:topRightRadius="@dimen/restore_tabs_outer_corner_radius"
+        android:bottomLeftRadius="@dimen/restore_tabs_inner_corner_radius"
+        android:bottomRightRadius="@dimen/restore_tabs_inner_corner_radius" />
+    </org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
+  </item>
+</org.chromium.chrome.browser.ui.fast_checkout.UnsharableRippleDrawable>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_device_icon.xml b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_phone_icon.xml
similarity index 100%
rename from chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_device_icon.xml
rename to chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_phone_icon.xml
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_tablet_icon.xml b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_tablet_icon.xml
new file mode 100644
index 0000000..808808f
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/res/drawable/restore_tabs_tablet_icon.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M100,800q-24.75,0 -42.38,-17.63T40,740L40,220q0,-24.75 17.63,-42.38T100,160h760q24.75,0 42.38,17.63T920,220v520q0,24.75 -17.63,42.38T860,800L100,800ZM130,220h-30v520h30L130,220ZM190,740h580L770,220L190,220v520ZM830,220v520h30L860,220h-30ZM830,220h30,-30ZM130,220h-30,30Z"/>
+</vector>
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_bottom_sheet.xml b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_bottom_sheet.xml
index 708a596..20ed2c8 100644
--- a/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_bottom_sheet.xml
+++ b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_bottom_sheet.xml
@@ -22,4 +22,7 @@
     <include
         android:id="@+id/restore_tabs_promo_screen_sheet"
         layout="@layout/restore_tabs_promo_screen_sheet" />
+    <include
+        android:id="@+id/restore_tabs_detail_screen_sheet"
+        layout="@layout/restore_tabs_detail_screen_sheet" />
 </ViewFlipper>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_detail_screen_sheet.xml b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_detail_screen_sheet.xml
new file mode 100644
index 0000000..3af6ed0
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_detail_screen_sheet.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:layout_marginTop="12dp"
+    android:background="@android:color/transparent"
+    android:orientation="vertical">
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <ImageButton
+            android:id="@+id/restore_tabs_toolbar_back_image_button"
+            android:layout_width="wrap_content"
+            android:layout_height="?attr/actionBarSize"
+            android:paddingLeft="20dp"
+            android:paddingRight="20dp"
+            android:src="@drawable/ic_arrow_back_24dp"
+            app:tint="@macro/default_icon_color"
+            android:background="?attr/actionBarItemBackground"
+            android:contentDescription="@string/restore_tabs_back_to_promo_screen_icon_description" />
+        <TextView
+            android:id="@+id/restore_tabs_toolbar_title_text_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:ellipsize="end"
+            android:singleLine="true"
+            style="@style/TextAppearance.Headline"/>
+    </LinearLayout>
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/restore_tabs_detail_screen_recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipToPadding="false"
+        android:divider="@null"
+        android:paddingBottom="24dp"
+        android:paddingLeft="@dimen/restore_tabs_sheet_padding_content"
+        android:paddingRight="@dimen/restore_tabs_sheet_padding_content"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_foreign_session_item.xml b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_foreign_session_item.xml
new file mode 100644
index 0000000..87077f3
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_foreign_session_item.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/restore_tabs_detail_sheet_device_view"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:paddingTop="@dimen/restore_tabs_detail_sheet_padding_vertical"
+    android:paddingBottom="@dimen/restore_tabs_detail_sheet_padding_vertical"
+    android:orientation="horizontal">
+    <ImageView
+        android:id="@+id/restore_tabs_device_sheet_device_icon"
+        android:layout_width="@dimen/restore_tabs_device_icon"
+        android:layout_height="@dimen/restore_tabs_device_icon"
+        app:tint="@macro/default_icon_color"
+        android:background="@null"
+        android:layout_marginStart="@dimen/restore_tabs_device_icon_margin_horizontal"
+        android:layout_marginEnd="@dimen/restore_tabs_device_icon_margin_horizontal"
+        android:layout_gravity="center_vertical"
+        android:importantForAccessibility="no" />
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical">
+            <TextView
+                android:id="@+id/restore_tabs_detail_sheet_device_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextAppearance.TextMedium.Primary"
+                android:textAlignment="viewStart" />
+            <TextView
+                android:id="@+id/restore_tabs_detail_sheet_session_info"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+                android:textAlignment="viewStart" />
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/restore_tabs_detail_sheet_device_item_selected_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/restore_tabs_detail_sheet_selected_icon_margin_start"
+        android:layout_marginEnd="@dimen/restore_tabs_detail_sheet_selected_icon_margin_end"
+        android:layout_gravity="center_vertical"
+        android:contentDescription="@null"
+        app:tint="?attr/colorPrimary"
+        app:srcCompat="@drawable/ic_check_circle_filled_green_24dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_promo_screen_sheet.xml b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_promo_screen_sheet.xml
index 455b8fa..450f62a5 100644
--- a/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_promo_screen_sheet.xml
+++ b/chrome/browser/recent_tabs/internal/android/java/res/layout/restore_tabs_promo_screen_sheet.xml
@@ -42,7 +42,7 @@
     </LinearLayout>
     <!-- Selected Device  -->
     <LinearLayout
-        android:id="@+id/selected_device_view"
+        android:id="@+id/restore_tabs_selected_device_view"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
         android:layout_marginTop="24dp"
@@ -52,14 +52,15 @@
         android:background="@drawable/restore_tabs_device_info_background"
         android:orientation="horizontal">
       <ImageView
+          android:id="@+id/restore_tabs_promo_sheet_device_icon"
           android:layout_width="@dimen/restore_tabs_device_icon"
           android:layout_height="@dimen/restore_tabs_device_icon"
+          app:tint="@macro/default_icon_color"
           android:background="@null"
           android:layout_marginStart="@dimen/restore_tabs_device_icon_margin_horizontal"
           android:layout_marginEnd="@dimen/restore_tabs_device_icon_margin_horizontal"
           android:layout_gravity="center_vertical"
-          android:importantForAccessibility="no"
-          app:srcCompat="@drawable/restore_tabs_device_icon" />
+          android:importantForAccessibility="no" />
       <LinearLayout
         android:layout_height="wrap_content"
         android:layout_width="0dp"
@@ -87,8 +88,7 @@
           android:layout_gravity="center_vertical"
           android:importantForAccessibility="no"
           android:contentDescription="@string/restore_tabs_promo_sheet_expand_icon_device_info_description"
-          app:tint="@color/default_icon_color_secondary_tint_list"
-          app:srcCompat="@drawable/restore_tabs_expand_more" />
+          app:tint="@color/default_icon_color_secondary_tint_list" />
     </LinearLayout>
     <!-- Action buttons section -->
     <org.chromium.ui.widget.ButtonCompat
diff --git a/chrome/browser/recent_tabs/internal/android/java/res/values/dimens.xml b/chrome/browser/recent_tabs/internal/android/java/res/values/dimens.xml
index aea4118..7f1e106f 100644
--- a/chrome/browser/recent_tabs/internal/android/java/res/values/dimens.xml
+++ b/chrome/browser/recent_tabs/internal/android/java/res/values/dimens.xml
@@ -8,13 +8,21 @@
 <resources>
     <!-- General dimensions. -->
     <dimen name="restore_tabs_sheet_padding">10dp</dimen>
+    <dimen name="restore_tabs_sheet_padding_content">14dp</dimen>
     <dimen name="restore_tabs_device_icon">20dp</dimen>
     <dimen name="restore_tabs_device_icon_margin_horizontal">18dp</dimen>
     <dimen name="restore_tabs_fre_logo_height">100dp</dimen>
+    <dimen name="restore_tabs_inner_corner_radius">4dp</dimen>
     <dimen name="restore_tabs_outer_corner_radius">16dp</dimen>
 
     <!-- Home sheet dimensions. -->
     <dimen name="restore_tabs_promo_sheet_device_card_padding">16dp</dimen>
     <dimen name="restore_tabs_promo_sheet_expand_option_icon_margin_start">16dp</dimen>
     <dimen name="restore_tabs_promo_sheet_expand_option_icon_margin_end">8dp</dimen>
+
+    <!-- Detail sheet dimensions. -->
+    <dimen name="restore_tabs_detail_sheet_padding_vertical">16dp</dimen>
+    <dimen name="restore_tabs_detail_sheet_spacing_vertical">2dp</dimen>
+    <dimen name="restore_tabs_detail_sheet_selected_icon_margin_start">8dp</dimen>
+    <dimen name="restore_tabs_detail_sheet_selected_icon_margin_end">16dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinator.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinator.java
index 49588db..e3fee93 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinator.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinator.java
@@ -13,6 +13,7 @@
 
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType;
+import org.chromium.chrome.browser.recent_tabs.ui.RestoreTabsDetailScreenCoordinator;
 import org.chromium.chrome.browser.recent_tabs.ui.RestoreTabsPromoScreenCoordinator;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -51,6 +52,10 @@
         RestoreTabsPromoScreenCoordinator restoreTabsPromoScreenCoordinator =
                 new RestoreTabsPromoScreenCoordinator(restoreTabsPromoScreenView, mModel);
 
+        View detailScreenView = rootView.findViewById(R.id.restore_tabs_detail_screen_sheet);
+        RestoreTabsDetailScreenCoordinator restoreTabsDetailScreenCoordinator =
+                new RestoreTabsDetailScreenCoordinator(context, detailScreenView, mModel);
+
         mViewFlipperView =
                 (ViewFlipper) rootView.findViewById(R.id.restore_tabs_bottom_sheet_view_flipper);
         mModel.addObserver((source, propertyKey) -> {
@@ -66,6 +71,10 @@
         switch (screenType) {
             case ScreenType.HOME_SCREEN:
                 return 0;
+            // Both the device and review tabs selection screens are displayed on the detail screen.
+            case ScreenType.DEVICE_SCREEN:
+            case ScreenType.REVIEW_TABS_SCREEN:
+                return 1;
         }
         assert false : "Undefined ScreenType: " + screenType;
         return 0;
@@ -89,4 +98,9 @@
     ViewFlipper getViewFlipperForTesting() {
         return mViewFlipperView;
     }
+
+    @VisibleForTesting
+    View getContentViewForTesting() {
+        return mContent.getContentView();
+    }
 }
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinatorUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinatorUnitTest.java
index da63a254..992f649 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinatorUnitTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsCoordinatorUnitTest.java
@@ -8,6 +8,8 @@
 import static org.mockito.Mockito.verify;
 
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.CURRENT_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_TITLE;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.DEVICE_SCREEN;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.HOME_SCREEN;
 
 import android.app.Activity;
@@ -76,5 +78,9 @@
     public void testRestoreTabsCoordinator_viewFlipperScreenChange() {
         mModel.set(CURRENT_SCREEN, HOME_SCREEN);
         Assert.assertEquals(0, mViewFlipperView.getDisplayedChild());
+
+        mModel.set(DETAIL_SCREEN_TITLE, R.string.restore_tabs_device_screen_sheet_title);
+        mModel.set(CURRENT_SCREEN, DEVICE_SCREEN);
+        Assert.assertEquals(1, mViewFlipperView.getDisplayedChild());
     }
 }
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediator.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediator.java
index 60fc8be..ac07f42 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediator.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediator.java
@@ -21,6 +21,7 @@
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -115,6 +116,11 @@
         assert sessions != null && sessions.size() != 0;
 
         ForeignSession previousSelection = mModel.get(RestoreTabsProperties.SELECTED_DEVICE);
+
+        // Sort the incoming list of foreign sessions by the most recent modified time.
+        Collections.sort(sessions,
+                (ForeignSession s1,
+                        ForeignSession s2) -> Long.compare(s2.modifiedTime, s1.modifiedTime));
         ForeignSession newSelection = sessions.get(0);
 
         // Populate all model entries.
@@ -220,6 +226,8 @@
         if (screenType == RestoreTabsProperties.ScreenType.DEVICE_SCREEN) {
             mModel.set(RestoreTabsProperties.DETAIL_SCREEN_MODEL_LIST,
                     mModel.get(RestoreTabsProperties.DEVICE_MODEL_LIST));
+            mModel.set(RestoreTabsProperties.DETAIL_SCREEN_TITLE,
+                    R.string.restore_tabs_device_screen_sheet_title);
             mModel.set(RestoreTabsProperties.REVIEW_TABS_SCREEN_DELEGATE, null);
         } else if (screenType == RestoreTabsProperties.ScreenType.REVIEW_TABS_SCREEN) {
             mModel.set(RestoreTabsProperties.DETAIL_SCREEN_MODEL_LIST,
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediatorUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediatorUnitTest.java
index 1434f18..e173877c 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediatorUnitTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsMediatorUnitTest.java
@@ -13,6 +13,7 @@
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.CURRENT_SCREEN;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_BACK_CLICK_HANDLER;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_MODEL_LIST;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_TITLE;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DEVICE_MODEL_LIST;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.HOME_SCREEN_DELEGATE;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.NUM_TABS_DESELECTED;
@@ -43,6 +44,7 @@
 import org.chromium.chrome.browser.recent_tabs.ui.RestoreTabsPromoScreenCoordinator;
 import org.chromium.chrome.browser.recent_tabs.ui.TabItemProperties;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.components.sync_device_info.FormFactor;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -90,8 +92,8 @@
 
     @Test
     public void testRestoreTabsMediator_onDismissed() {
-        ForeignSession session =
-                new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>());
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
         List<ForeignSession> testSessions = new ArrayList<>();
         testSessions.add(session);
 
@@ -104,8 +106,8 @@
 
     @Test
     public void testRestoreTabsMediator_showOptionsUpdatesModel() {
-        ForeignSession session =
-                new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>());
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
         List<ForeignSession> testSessions = new ArrayList<>();
         testSessions.add(session);
 
@@ -149,8 +151,8 @@
         tabs.add(tab1);
         tabs.add(tab2);
 
-        ForeignSession session =
-                new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>());
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
         mModel.set(SELECTED_DEVICE, session);
 
         delegate.onAllTabsChosen();
@@ -162,10 +164,10 @@
 
     @Test
     public void testRestoreTabsMediator_setDeviceListItemsNoSelection() {
-        ForeignSession session1 =
-                new ForeignSession("tag1", "John's iPhone 6", 32L, new ArrayList<>());
-        ForeignSession session2 =
-                new ForeignSession("tag2", "John's iPhone 7", 33L, new ArrayList<>());
+        ForeignSession session1 = new ForeignSession(
+                "tag1", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+        ForeignSession session2 = new ForeignSession(
+                "tag2", "John's iPhone 7", 33L, new ArrayList<>(), FormFactor.PHONE);
         List<ForeignSession> testSessions = new ArrayList<>();
         testSessions.add(session1);
         testSessions.add(session2);
@@ -177,10 +179,10 @@
 
     @Test
     public void testRestoreTabsMediator_setDeviceListItemsSelection() {
-        ForeignSession session1 =
-                new ForeignSession("tag1", "John's iPhone 6", 32L, new ArrayList<>());
-        ForeignSession session2 =
-                new ForeignSession("tag2", "John's iPhone 7", 33L, new ArrayList<>());
+        ForeignSession session1 = new ForeignSession(
+                "tag1", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+        ForeignSession session2 = new ForeignSession(
+                "tag2", "John's iPhone 7", 33L, new ArrayList<>(), FormFactor.PHONE);
         List<ForeignSession> testSessions = new ArrayList<>();
         testSessions.add(session1);
         testSessions.add(session2);
@@ -189,13 +191,18 @@
 
         mMediator.setDeviceListItems(testSessions);
 
-        Assert.assertEquals(mModel.get(SELECTED_DEVICE), testSessions.get(1));
+        // Resulting selected device sorted based on recent modified time.
+        Assert.assertEquals(mModel.get(SELECTED_DEVICE), testSessions.get(0));
+        Assert.assertEquals(testSessions.get(0).tag, session2.tag);
+        Assert.assertEquals(testSessions.get(0).name, session2.name);
+        Assert.assertEquals(testSessions.get(0).modifiedTime, session2.modifiedTime);
+        Assert.assertEquals(testSessions.get(0).formFactor, session2.formFactor);
     }
 
     @Test
     public void testRestoreTabsMediator_setSelectedDeviceItem() {
-        ForeignSession session =
-                new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>());
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
         mMediator.setSelectedDeviceItem(session);
 
         Assert.assertEquals(mModel.get(SELECTED_DEVICE), session);
@@ -225,7 +232,8 @@
         List<ForeignSessionWindow> windows = new ArrayList<>();
         windows.add(window);
 
-        ForeignSession session = new ForeignSession("tag", "John's iPhone 6", 32L, windows);
+        ForeignSession session =
+                new ForeignSession("tag", "John's iPhone 6", 32L, windows, FormFactor.PHONE);
         mMediator.setSelectedDeviceItem(session);
 
         Assert.assertEquals(mModel.get(SELECTED_DEVICE), session);
@@ -239,6 +247,8 @@
         mMediator.setCurrentScreen(DEVICE_SCREEN);
 
         Assert.assertEquals(mModel.get(DETAIL_SCREEN_MODEL_LIST), mModel.get(DEVICE_MODEL_LIST));
+        Assert.assertEquals(
+                mModel.get(DETAIL_SCREEN_TITLE), R.string.restore_tabs_device_screen_sheet_title);
         Assert.assertNull(mModel.get(REVIEW_TABS_SCREEN_DELEGATE));
         Assert.assertEquals(mModel.get(CURRENT_SCREEN), DEVICE_SCREEN);
     }
@@ -274,7 +284,7 @@
         List<ForeignSessionWindow> windows = new ArrayList<>();
         windows.add(window);
 
-        ForeignSession session = new ForeignSession("tag", "John's iPhone 6", 32L, windows);
+        ForeignSession session = new ForeignSession("tag", "John's iPhone 6", 32L, windows, 2);
         mModel.set(SELECTED_DEVICE, session);
         mMediator.setTabListItems();
 
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsUiRenderTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsUiRenderTest.java
new file mode 100644
index 0000000..7168469
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/RestoreTabsUiRenderTest.java
@@ -0,0 +1,255 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs;
+
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.CURRENT_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DEVICE_MODEL_LIST;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.SELECTED_DEVICE;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.HOME_SCREEN;
+
+import android.app.Activity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.test.params.ParameterAnnotations;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.DoNotBatch;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSession;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSessionTab;
+import org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DetailItemType;
+import org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties;
+import org.chromium.chrome.browser.recent_tabs.ui.TabItemProperties;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
+import org.chromium.chrome.test.util.ChromeRenderTestRule;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.sync_device_info.FormFactor;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.test.util.BlankUiTestActivity;
+import org.chromium.ui.test.util.DisableAnimationsTestRule;
+import org.chromium.ui.test.util.NightModeTestUtils;
+import org.chromium.ui.test.util.RenderTestRule;
+import org.chromium.ui.test.util.ViewUtils;
+import org.chromium.url.JUnitTestGURLs;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Render tests for Restore Tabs UI elements */
+@RunWith(ParameterizedRunner.class)
+@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+@DoNotBatch(reason = "RestoreTabs tests different startup behaviours and shouldn't be batched.")
+public class RestoreTabsUiRenderTest {
+    @ParameterAnnotations.ClassParameter
+    public static List<ParameterSet> sClassParams =
+            new NightModeTestUtils.NightModeParams().getParameters();
+
+    @Rule
+    public final ChromeRenderTestRule mRenderTestRule =
+            ChromeRenderTestRule.Builder.withPublicCorpus()
+                    .setBugComponent(RenderTestRule.Component.UI_BROWSER_MOBILE_RECENT_TABS)
+                    .setRevision(1)
+                    .build();
+    @Rule
+    public BaseActivityTestRule<BlankUiTestActivity> mActivityTestRule =
+            new BaseActivityTestRule<>(BlankUiTestActivity.class);
+    @Rule
+    public JniMocker jniMocker = new JniMocker();
+    @Rule
+    public final DisableAnimationsTestRule mDisableAnimationsRule = new DisableAnimationsTestRule();
+
+    @Mock
+    ForeignSessionHelper.Natives mForeignSessionHelperJniMock;
+    @Mock
+    private Profile mProfile;
+    @Mock
+    private RestoreTabsControllerFactory.ControllerListener mListener;
+    @Mock
+    private TabCreatorManager mTabCreatorManager;
+    @Mock
+    private BottomSheetController mBottomSheetController;
+
+    private RestoreTabsCoordinator mCoordinator;
+    private View mView;
+    private PropertyModel mModel;
+    private FrameLayout mRootView;
+
+    public RestoreTabsUiRenderTest(boolean nightModeEnabled) {
+        NightModeTestUtils.setUpNightModeForBlankUiTestActivity(nightModeEnabled);
+        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
+    }
+
+    @Before
+    public void setUp() throws InterruptedException {
+        MockitoAnnotations.initMocks(this);
+        Profile.setLastUsedProfileForTesting(mProfile);
+        jniMocker.mock(ForeignSessionHelperJni.TEST_HOOKS, mForeignSessionHelperJniMock);
+        mActivityTestRule.launchActivity(null);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Activity activity = mActivityTestRule.getActivity();
+
+            mCoordinator = new RestoreTabsCoordinator(mActivityTestRule.getActivity(), mProfile,
+                    mListener, mTabCreatorManager, mBottomSheetController);
+            mView = mCoordinator.getContentViewForTesting();
+            mView.setBackground(
+                    AppCompatResources.getDrawable(activity, R.drawable.menu_bg_tinted));
+            mModel = mCoordinator.getPropertyModelForTesting();
+
+            mRootView = new FrameLayout(activity);
+            activity.setContentView(mRootView);
+            mRootView.addView(mView);
+        });
+    }
+
+    @After
+    public void tearDownTest() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> NightModeTestUtils.tearDownNightModeForBlankUiTestActivity());
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
+    public void testPromoScreenSheet() throws IOException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // 0 devices in DEVICE_MODEL_LIST and 1 selected tab in REVIEW_TABS_MODEL_LIST.
+            // Restore tabs button enabled and chevron/onClickListener for device view.
+            ForeignSessionTab tab = new ForeignSessionTab(
+                    JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_1), "title", 32L, 0);
+            ForeignSession session = new ForeignSession(
+                    "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+
+            ModelList tabItems = mModel.get(RestoreTabsProperties.REVIEW_TABS_MODEL_LIST);
+            tabItems.clear();
+            PropertyModel model = TabItemProperties.create(/*tab=*/tab, /*isSelected=*/true);
+            model.set(TabItemProperties.ON_CLICK_LISTENER, () -> {});
+            tabItems.add(new ListItem(DetailItemType.TAB, model));
+
+            mModel.set(SELECTED_DEVICE, session);
+            mModel.set(CURRENT_SCREEN, HOME_SCREEN);
+        });
+
+        ViewUtils.waitForView(mRootView, withId(R.id.restore_tabs_promo_screen_sheet));
+        Thread.sleep(2000);
+        mRenderTestRule.render(mRootView, "restore_tabs_promo_screen_all_enabled");
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
+    public void testPromoScreenSheet_disabledDeviceViewAndRestoreButtonWithTabletIcon()
+            throws IOException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // 1 device in DEVICE_MODEL_LIST and 0 selected tabs in REVIEW_TABS_MODEL_LIST.
+            // Restore tabs button disabled, tablet icon and no chevron/onClickListener for device
+            // view.
+            ForeignSession session = new ForeignSession(
+                    "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.TABLET);
+
+            ModelList sessionItems = mModel.get(DEVICE_MODEL_LIST);
+            sessionItems.clear();
+            PropertyModel model = ForeignSessionItemProperties.create(
+                    /*session=*/session, /*isSelected=*/false, /*onClickListener=*/() -> {});
+            sessionItems.add(new ListItem(DetailItemType.DEVICE, model));
+
+            mModel.set(SELECTED_DEVICE, session);
+            mModel.set(CURRENT_SCREEN, HOME_SCREEN);
+        });
+
+        ViewUtils.waitForView(mRootView, withId(R.id.restore_tabs_promo_screen_sheet));
+        Thread.sleep(2000);
+        mRenderTestRule.render(mRootView, "restore_tabs_promo_screen_disabled_elements");
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
+    public void testDeviceScreenSheet_twoItemDecorationAndSelection()
+            throws IOException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ForeignSession session1 = new ForeignSession(
+                    "tag1", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+            ForeignSession session2 = new ForeignSession(
+                    "tag2", "John's iPhone 7", 33L, new ArrayList<>(), FormFactor.PHONE);
+
+            List<ForeignSession> sessions = new ArrayList<>();
+            sessions.add(session1);
+            sessions.add(session2);
+
+            ModelList sessionItems = mModel.get(DEVICE_MODEL_LIST);
+            sessionItems.clear();
+            for (ForeignSession session : sessions) {
+                PropertyModel model = ForeignSessionItemProperties.create(
+                        /*session=*/session, /*isSelected=*/true, /*onClickListener=*/() -> {});
+                sessionItems.add(new ListItem(DetailItemType.DEVICE, model));
+            }
+
+            mModel.set(CURRENT_SCREEN, HOME_SCREEN);
+            mView.findViewById(R.id.restore_tabs_selected_device_view).performClick();
+        });
+
+        ViewUtils.waitForView(mRootView, withId(R.id.restore_tabs_detail_screen_sheet));
+        Thread.sleep(2000);
+        mRenderTestRule.render(mRootView, "restore_tabs_detail_screen_two_item_decoration");
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
+    public void testDeviceScreenSheet_threeItemDecorationAndTablet()
+            throws IOException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ForeignSession session1 = new ForeignSession(
+                    "tag1", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+            ForeignSession session2 = new ForeignSession(
+                    "tag2", "John's iPhone 7", 33L, new ArrayList<>(), FormFactor.PHONE);
+            ForeignSession session3 = new ForeignSession(
+                    "tag3", "John's iPad Air", 34L, new ArrayList<>(), FormFactor.TABLET);
+
+            List<ForeignSession> sessions = new ArrayList<>();
+            sessions.add(session1);
+            sessions.add(session2);
+            sessions.add(session3);
+
+            ModelList sessionItems = mModel.get(DEVICE_MODEL_LIST);
+            sessionItems.clear();
+            for (ForeignSession session : sessions) {
+                PropertyModel model = ForeignSessionItemProperties.create(
+                        /*session=*/session, /*isSelected=*/false, /*onClickListener=*/() -> {});
+                sessionItems.add(new ListItem(DetailItemType.DEVICE, model));
+            }
+
+            mModel.set(CURRENT_SCREEN, HOME_SCREEN);
+            mView.findViewById(R.id.restore_tabs_selected_device_view).performClick();
+        });
+
+        ViewUtils.waitForView(mRootView, withId(R.id.restore_tabs_detail_screen_sheet));
+        Thread.sleep(2000);
+        mRenderTestRule.render(mRootView, "restore_tabs_detail_screen_three_item_decoration");
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemPropertiesUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemPropertiesUnitTest.java
index c1df67bd..38ef5f8 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemPropertiesUnitTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemPropertiesUnitTest.java
@@ -30,7 +30,7 @@
 
     @Before
     public void setUp() {
-        mSession = new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>());
+        mSession = new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>(), 2);
         mModel = ForeignSessionItemProperties.create(
                 /*device=*/mSession, /*isSelected=*/false, /*onClickListener=*/() -> {});
     }
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinder.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinder.java
new file mode 100644
index 0000000..d3e476d
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinder.java
@@ -0,0 +1,70 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs.ui;
+
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.IS_SELECTED;
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.ON_CLICK_LISTENER;
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.SESSION_PROFILE;
+
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSession;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSessionWindow;
+import org.chromium.chrome.browser.recent_tabs.R;
+import org.chromium.components.sync_device_info.FormFactor;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** A binder class for device items on the detail sheet. */
+public class ForeignSessionItemViewBinder {
+    static View create(ViewGroup parent) {
+        return LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.restore_tabs_foreign_session_item, parent, false);
+    }
+
+    static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == SESSION_PROFILE) {
+            ForeignSession session = model.get(SESSION_PROFILE);
+            TextView sessionNameView =
+                    view.findViewById(R.id.restore_tabs_detail_sheet_device_name);
+            sessionNameView.setText(session.name);
+
+            ImageView deviceIconView =
+                    view.findViewById(R.id.restore_tabs_device_sheet_device_icon);
+            if (session.formFactor == FormFactor.PHONE) {
+                deviceIconView.setImageResource(R.drawable.restore_tabs_phone_icon);
+            } else if (session.formFactor == FormFactor.TABLET) {
+                deviceIconView.setImageResource(R.drawable.restore_tabs_tablet_icon);
+            }
+            assert (session.formFactor == FormFactor.PHONE
+                    || session.formFactor == FormFactor.TABLET)
+                : "Unsupported form factor device retrieved.";
+
+            int tabCount = 0;
+            for (ForeignSessionWindow window : session.windows) {
+                tabCount += window.tabs.size();
+            }
+
+            CharSequence lastModifiedTimeString = DateUtils.getRelativeTimeSpanString(
+                    session.modifiedTime, System.currentTimeMillis(), 0);
+            String sessionInfo = view.getContext().getResources().getQuantityString(
+                    R.plurals.restore_tabs_promo_sheet_device_info, tabCount,
+                    Integer.toString(tabCount), lastModifiedTimeString);
+            TextView sessionInfoView =
+                    view.findViewById(R.id.restore_tabs_detail_sheet_session_info);
+            sessionInfoView.setText(sessionInfo);
+        } else if (propertyKey == ON_CLICK_LISTENER) {
+            view.setOnClickListener((v) -> model.get(ON_CLICK_LISTENER).run());
+        } else if (propertyKey == IS_SELECTED) {
+            view.findViewById(R.id.restore_tabs_detail_sheet_device_item_selected_icon)
+                    .setVisibility(model.get(IS_SELECTED) ? View.VISIBLE : View.GONE);
+        }
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinderUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinderUnitTest.java
new file mode 100644
index 0000000..363f86e
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/ForeignSessionItemViewBinderUnitTest.java
@@ -0,0 +1,145 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs.ui;
+
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.ALL_KEYS;
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.IS_SELECTED;
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.ON_CLICK_LISTENER;
+import static org.chromium.chrome.browser.recent_tabs.ui.ForeignSessionItemProperties.SESSION_PROFILE;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSession;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSessionTab;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSessionWindow;
+import org.chromium.chrome.browser.recent_tabs.R;
+import org.chromium.components.sync_device_info.FormFactor;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.url.JUnitTestGURLs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for ForeignSessionItemViewBinder. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ForeignSessionItemViewBinderUnitTest {
+    private Activity mActivity;
+    private View mForeignSessionItemView;
+    private View mForeignSessionItemView2;
+    private PropertyModel mModel;
+    private PropertyModel mModel2;
+    private PropertyModelChangeProcessor mPropertyModelChangeProcessor;
+    private PropertyModelChangeProcessor mPropertyModelChangeProcessor2;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
+        mForeignSessionItemView = LayoutInflater.from(mActivity).inflate(
+                R.layout.restore_tabs_foreign_session_item, /*root=*/null);
+
+        mModel = new PropertyModel.Builder(ALL_KEYS)
+                         .with(SESSION_PROFILE,
+                                 new ForeignSession("tag", "John's iPhone 6", 32L,
+                                         new ArrayList<>(), FormFactor.PHONE))
+                         .with(IS_SELECTED, true)
+                         .with(ON_CLICK_LISTENER,
+                                 () -> { mModel.set(IS_SELECTED, !mModel.get(IS_SELECTED)); })
+                         .build();
+
+        mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
+                mModel, mForeignSessionItemView, ForeignSessionItemViewBinder::bind);
+
+        ForeignSessionTab tab = new ForeignSessionTab(
+                JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_1), "title", 32L, 0);
+        List<ForeignSessionTab> tabs = new ArrayList<>();
+        tabs.add(tab);
+
+        ForeignSessionWindow window = new ForeignSessionWindow(31L, 1, tabs);
+        List<ForeignSessionWindow> windows = new ArrayList<>();
+        windows.add(window);
+
+        mForeignSessionItemView2 = LayoutInflater.from(mActivity).inflate(
+                R.layout.restore_tabs_foreign_session_item, /*root=*/null);
+        mModel2 = new PropertyModel.Builder(ALL_KEYS)
+                          .with(SESSION_PROFILE,
+                                  new ForeignSession("tag2", "John's iPad Air", 33L, windows,
+                                          FormFactor.TABLET))
+                          .with(IS_SELECTED, true)
+                          .with(ON_CLICK_LISTENER,
+                                  () -> { mModel.set(IS_SELECTED, !mModel.get(IS_SELECTED)); })
+                          .build();
+        mPropertyModelChangeProcessor2 = PropertyModelChangeProcessor.create(
+                mModel2, mForeignSessionItemView2, ForeignSessionItemViewBinder::bind);
+    }
+
+    @Test
+    public void testSessionProfile_phone() {
+        mModel.get(SESSION_PROFILE);
+        TextView sessionNameView =
+                mForeignSessionItemView.findViewById(R.id.restore_tabs_detail_sheet_device_name);
+        Assert.assertEquals("John's iPhone 6", sessionNameView.getText());
+
+        ImageView deviceIconView =
+                mForeignSessionItemView.findViewById(R.id.restore_tabs_device_sheet_device_icon);
+        Assert.assertNotNull(deviceIconView.getDrawable());
+
+        TextView sessionInfoView =
+                mForeignSessionItemView.findViewById(R.id.restore_tabs_detail_sheet_session_info);
+        Assert.assertEquals("0 tabs, last updated: Jan 1, 1970", sessionInfoView.getText());
+    }
+
+    @Test
+    public void testOnClickListener() {
+        mModel.get(ON_CLICK_LISTENER);
+        View onClickListener =
+                mForeignSessionItemView.findViewById(R.id.restore_tabs_detail_sheet_device_view);
+        Assert.assertNotNull(onClickListener);
+        onClickListener.performClick();
+        Assert.assertFalse(mModel.get(IS_SELECTED));
+    }
+
+    @Test
+    public void testSetIsSelected() {
+        ImageView selectedIcon1 = mForeignSessionItemView.findViewById(
+                R.id.restore_tabs_detail_sheet_device_item_selected_icon);
+        Assert.assertEquals(View.VISIBLE, selectedIcon1.getVisibility());
+
+        mModel.set(IS_SELECTED, false);
+
+        ImageView selectedIcon2 = mForeignSessionItemView.findViewById(
+                R.id.restore_tabs_detail_sheet_device_item_selected_icon);
+        Assert.assertEquals(View.GONE, selectedIcon2.getVisibility());
+    }
+
+    @Test
+    public void testSessionProfile_tablet() {
+        mModel2.get(SESSION_PROFILE);
+        TextView sessionNameView =
+                mForeignSessionItemView2.findViewById(R.id.restore_tabs_detail_sheet_device_name);
+        Assert.assertEquals("John's iPad Air", sessionNameView.getText());
+
+        ImageView deviceIconView =
+                mForeignSessionItemView2.findViewById(R.id.restore_tabs_device_sheet_device_icon);
+        Assert.assertNotNull(deviceIconView.getDrawable());
+
+        TextView sessionInfoView =
+                mForeignSessionItemView2.findViewById(R.id.restore_tabs_detail_sheet_session_info);
+        Assert.assertEquals("1 tab, last updated: Jan 1, 1970", sessionInfoView.getText());
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailItemDecoration.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailItemDecoration.java
new file mode 100644
index 0000000..bf5a9d8c
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailItemDecoration.java
@@ -0,0 +1,71 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs.ui;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Px;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.chromium.chrome.browser.recent_tabs.R;
+
+/**
+ * The item decoration used on the device and review tabs selection screens that
+ * adds horizontal spacing between elements and chooses the correct item background
+ * depending on the position of the item in the list.
+ */
+public class RestoreTabsDetailItemDecoration extends RecyclerView.ItemDecoration {
+    private final int mVerticalSpacing;
+
+    /**
+     * Creates a RestoreTabsDetailItemDecoration.
+     * @param verticalSpacing The spacing between items in pixels.
+     */
+    public RestoreTabsDetailItemDecoration(@Px int verticalSpacing) {
+        mVerticalSpacing = verticalSpacing;
+    }
+
+    @Override
+    public void getItemOffsets(
+            Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        // The first item does not get additional spacing.
+        outRect.top = (parent.getChildAdapterPosition(view) != 0) ? mVerticalSpacing : 0;
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        for (int index = 0; index < parent.getChildCount(); ++index) {
+            View child = parent.getChildAt(index);
+            int positionInAdapter = parent.getChildAdapterPosition(child);
+            child.setBackground(AppCompatResources.getDrawable(parent.getContext(),
+                    getBackgroundDrawable(positionInAdapter, parent.getAdapter().getItemCount())));
+        }
+    }
+
+    /**
+     * Returns the appropriate item background based on the position of the item in the list.
+     * The first item has strongly rounded upper corners, the middle item has weakly
+     * rounded corners on the top and the bottom, and the last item has strongly rounded
+     * bottom corners.
+     * @param position The zero-indexed position in the adapter.
+     * @param itemCount The number of items in the adapter.
+     * @return The resource ID of the item background.
+     */
+    private static @DrawableRes int getBackgroundDrawable(int position, int itemCount) {
+        if (position == 0) {
+            return R.drawable.restore_tabs_detail_item_background_top;
+        }
+
+        if (position == itemCount - 1) {
+            return R.drawable.restore_tabs_detail_item_background_bottom;
+        }
+
+        return R.drawable.restore_tabs_detail_item_background_middle;
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenCoordinator.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenCoordinator.java
index a62a73f4..913326e 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenCoordinator.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenCoordinator.java
@@ -4,10 +4,22 @@
 
 package org.chromium.chrome.browser.recent_tabs.ui;
 
+import android.content.Context;
+import android.view.View;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.chromium.chrome.browser.recent_tabs.R;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
 /**
  * Coordinator for the detail screens (device select, review tabs) of the Restore Tabs on FRE promo.
  */
 public class RestoreTabsDetailScreenCoordinator {
+    private final RecyclerView mRecyclerView;
+
     /** The delegate of the class. */
     public interface Delegate {
         /** The user clicked on the select/deselect all tabs item. */
@@ -15,4 +27,20 @@
         /** The user clicked on restoring all selected tabs. */
         void onSelectedTabsChosen();
     }
+
+    public RestoreTabsDetailScreenCoordinator(Context context, View view, PropertyModel model) {
+        mRecyclerView = view.findViewById(R.id.restore_tabs_detail_screen_recycler_view);
+        LinearLayoutManager layoutManager =
+                new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
+        mRecyclerView.setLayoutManager(layoutManager);
+        mRecyclerView.addItemDecoration(
+                new RestoreTabsDetailItemDecoration(context.getResources().getDimensionPixelSize(
+                        R.dimen.restore_tabs_detail_sheet_spacing_vertical)));
+
+        RestoreTabsDetailScreenViewBinder.ViewHolder viewHolder =
+                new RestoreTabsDetailScreenViewBinder.ViewHolder(view);
+
+        PropertyModelChangeProcessor.create(
+                model, viewHolder, RestoreTabsDetailScreenViewBinder::bind);
+    }
 }
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinder.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinder.java
new file mode 100644
index 0000000..d99b40af6
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinder.java
@@ -0,0 +1,98 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs.ui;
+
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.CURRENT_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_BACK_CLICK_HANDLER;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_MODEL_LIST;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_TITLE;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.DEVICE_SCREEN;
+
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.chromium.chrome.browser.recent_tabs.R;
+import org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DetailItemType;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
+
+/**
+ * This class is responsible for pushing updates to the Restore Tabs detail screen view. These
+ * updates are pulled from the RestoreTabsProperties when a notification of an update is
+ * received.
+ */
+public class RestoreTabsDetailScreenViewBinder {
+    static class ViewHolder {
+        final View mContentView;
+
+        ViewHolder(View contentView) {
+            mContentView = contentView;
+        }
+
+        public void setAdapter(RecyclerView.Adapter adapter, ViewHolder view) {
+            getRecyclerView(view).setAdapter(adapter);
+        }
+    }
+
+    // This binder handles logic that targets when the CURRENT_SCREEN switches to DEVICE_SCREEN or
+    // REVIEW_TABS_SCREEN.
+    public static void bind(PropertyModel model, ViewHolder view, PropertyKey propertyKey) {
+        int currentScreen = model.get(CURRENT_SCREEN);
+
+        if (propertyKey == CURRENT_SCREEN) {
+            if (currentScreen == DEVICE_SCREEN) {
+                RestoreTabsViewBinderHelper.allKeysBinder(
+                        model, view, RestoreTabsDetailScreenViewBinder::bindDeviceScreen);
+            }
+        } else if (currentScreen == DEVICE_SCREEN) {
+            bindDeviceScreen(model, view, propertyKey);
+        }
+    }
+
+    public static void bindDeviceScreen(
+            PropertyModel model, ViewHolder view, PropertyKey propertyKey) {
+        bindCommonProperties(model, view, propertyKey);
+
+        if (propertyKey == DETAIL_SCREEN_MODEL_LIST) {
+            if (model.get(DETAIL_SCREEN_MODEL_LIST) == null) {
+                return;
+            }
+
+            SimpleRecyclerViewAdapter adapter =
+                    new SimpleRecyclerViewAdapter(model.get(DETAIL_SCREEN_MODEL_LIST));
+            adapter.registerType(DetailItemType.DEVICE, ForeignSessionItemViewBinder::create,
+                    ForeignSessionItemViewBinder::bind);
+            view.setAdapter(adapter, view);
+        }
+    }
+
+    private static void bindCommonProperties(
+            PropertyModel model, ViewHolder view, PropertyKey propertyKey) {
+        if (propertyKey == DETAIL_SCREEN_BACK_CLICK_HANDLER) {
+            getToolbarBackImageButton(view).setOnClickListener(
+                    (v) -> model.get(DETAIL_SCREEN_BACK_CLICK_HANDLER).run());
+        } else if (propertyKey == DETAIL_SCREEN_TITLE) {
+            String titleText = view.mContentView.getContext().getResources().getString(
+                    model.get(DETAIL_SCREEN_TITLE));
+            getToolbarTitleTextView(view).setText(titleText);
+        }
+    }
+
+    private static ImageButton getToolbarBackImageButton(ViewHolder view) {
+        return view.mContentView.findViewById(R.id.restore_tabs_toolbar_back_image_button);
+    }
+
+    private static TextView getToolbarTitleTextView(ViewHolder view) {
+        return view.mContentView.findViewById(R.id.restore_tabs_toolbar_title_text_view);
+    }
+
+    private static RecyclerView getRecyclerView(ViewHolder view) {
+        return view.mContentView.findViewById(R.id.restore_tabs_detail_screen_recycler_view);
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinderUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinderUnitTest.java
new file mode 100644
index 0000000..7aa1a0c
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsDetailScreenViewBinderUnitTest.java
@@ -0,0 +1,111 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs.ui;
+
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ALL_KEYS;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.CURRENT_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_BACK_CLICK_HANDLER;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_MODEL_LIST;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DETAIL_SCREEN_TITLE;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DEVICE_MODEL_LIST;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.NUM_TABS_DESELECTED;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.REVIEW_TABS_MODEL_LIST;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.DEVICE_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.HOME_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.VISIBLE;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSession;
+import org.chromium.chrome.browser.recent_tabs.R;
+import org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DetailItemType;
+import org.chromium.components.sync_device_info.FormFactor;
+import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+import java.util.ArrayList;
+
+/** Tests for RestoreTabsDetailScreenViewBinder. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class RestoreTabsDetailScreenViewBinderUnitTest {
+    private Activity mActivity;
+    private View mRestoreTabsDetailView;
+    private PropertyModel mModel;
+    private PropertyModelChangeProcessor mPropertyModelChangeProcessor;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
+        mRestoreTabsDetailView = LayoutInflater.from(mActivity).inflate(
+                R.layout.restore_tabs_bottom_sheet, /*root=*/null);
+
+        mModel = new PropertyModel.Builder(ALL_KEYS)
+                         .with(VISIBLE, false)
+                         .with(DEVICE_MODEL_LIST, new ModelList())
+                         .with(REVIEW_TABS_MODEL_LIST, new ModelList())
+                         .with(NUM_TABS_DESELECTED, 0)
+                         .build();
+
+        mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(mModel,
+                new RestoreTabsDetailScreenViewBinder.ViewHolder(mRestoreTabsDetailView),
+                RestoreTabsDetailScreenViewBinder::bind);
+    }
+
+    @Test
+    public void testSetDeviceScreen() {
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+        ModelList deviceItems = mModel.get(DEVICE_MODEL_LIST);
+        PropertyModel model = ForeignSessionItemProperties.create(
+                /*session=*/session, /*isSelected=*/true, /*onClickListener=*/() -> {});
+        deviceItems.add(new ListItem(DetailItemType.DEVICE, model));
+
+        mModel.set(DETAIL_SCREEN_TITLE, R.string.restore_tabs_device_screen_sheet_title);
+        mModel.set(DETAIL_SCREEN_BACK_CLICK_HANDLER,
+                () -> { mModel.set(CURRENT_SCREEN, HOME_SCREEN); });
+        mModel.set(CURRENT_SCREEN, DEVICE_SCREEN);
+
+        View backButton =
+                mRestoreTabsDetailView.findViewById(R.id.restore_tabs_toolbar_back_image_button);
+        Assert.assertNotNull(backButton);
+        backButton.performClick();
+        Assert.assertEquals(HOME_SCREEN, mModel.get(CURRENT_SCREEN));
+
+        TextView detailViewTitle =
+                mRestoreTabsDetailView.findViewById(R.id.restore_tabs_toolbar_title_text_view);
+        Assert.assertEquals("Open from", detailViewTitle.getText());
+
+        Assert.assertNotNull(
+                mRestoreTabsDetailView.findViewById(R.id.restore_tabs_detail_screen_recycler_view));
+    }
+
+    @Test
+    public void testOnDeviceScreen_setDetailScreenModelList() {
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+        ModelList deviceItems = mModel.get(DEVICE_MODEL_LIST);
+        PropertyModel model = ForeignSessionItemProperties.create(
+                /*session=*/session, /*isSelected=*/true, /*onClickListener=*/() -> {});
+        deviceItems.add(new ListItem(DetailItemType.DEVICE, model));
+
+        mModel.set(DETAIL_SCREEN_TITLE, R.string.restore_tabs_device_screen_sheet_title);
+        mModel.set(CURRENT_SCREEN, DEVICE_SCREEN);
+        mModel.set(DETAIL_SCREEN_MODEL_LIST, deviceItems);
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinder.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinder.java
index dabbbcd..b25524d 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinder.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinder.java
@@ -4,43 +4,34 @@
 
 package org.chromium.chrome.browser.recent_tabs.ui;
 
-import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ALL_KEYS;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.CURRENT_SCREEN;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DEVICE_MODEL_LIST;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.HOME_SCREEN_DELEGATE;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.NUM_TABS_DESELECTED;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.REVIEW_TABS_MODEL_LIST;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.SELECTED_DEVICE;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.HOME_SCREEN;
-import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.UNINITIALIZED;
 
 import android.text.format.DateUtils;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSession;
 import org.chromium.chrome.browser.recent_tabs.R;
 import org.chromium.chrome.browser.recent_tabs.ui.RestoreTabsPromoScreenCoordinator.Delegate;
+import org.chromium.components.sync_device_info.FormFactor;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.widget.ButtonCompat;
 
 /**
  * This class is responsible for pushing updates to the Restore Tabs promo screen view. These
- * updates are pulled from the RestoreTabsproperties when a notification of an update is
+ * updates are pulled from the RestoreTabsProperties when a notification of an update is
  * received.
  */
 public class RestoreTabsPromoScreenViewBinder {
-    /**
-     * A functional interface to perform a callback and run screen specific bind logic.
-     */
-    interface BindScreenCallback {
-        /**
-         * Perform bind logic on all property keys for the respective screen.
-         */
-        void bind(PropertyModel model, ViewHolder view, PropertyKey propertyKey);
-    }
-
     static class ViewHolder {
         final View mContentView;
 
@@ -49,25 +40,17 @@
         }
     }
 
+    // This binder handles logic that targets when the CURRENT_SCREEN switches to HOME_SCREEN.
     public static void bind(PropertyModel model, ViewHolder view, PropertyKey propertyKey) {
         int currentScreen = model.get(CURRENT_SCREEN);
 
         if (propertyKey == CURRENT_SCREEN) {
-            switch (currentScreen) {
-                case HOME_SCREEN:
-                    allKeysBinder(model, view, RestoreTabsPromoScreenViewBinder::bindHomeScreen);
-                    break;
-                default:
-                    assert currentScreen == UNINITIALIZED : "Switching to an unidentified screen.";
+            if (currentScreen == HOME_SCREEN) {
+                RestoreTabsViewBinderHelper.allKeysBinder(
+                        model, view, RestoreTabsPromoScreenViewBinder::bindHomeScreen);
             }
-        } else {
-            switch (currentScreen) {
-                case HOME_SCREEN:
-                    bindHomeScreen(model, view, propertyKey);
-                    break;
-                default:
-                    assert currentScreen == UNINITIALIZED : "Unidentified current screen.";
-            }
+        } else if (currentScreen == HOME_SCREEN) {
+            bindHomeScreen(model, view, propertyKey);
         }
     }
 
@@ -76,7 +59,15 @@
         if (propertyKey == HOME_SCREEN_DELEGATE) {
             Delegate delegate = model.get(HOME_SCREEN_DELEGATE);
 
-            getSelectedDeviceView(view).setOnClickListener((v) -> delegate.onShowDeviceList());
+            int numDevices = model.get(DEVICE_MODEL_LIST).size();
+            if (numDevices != 1) {
+                getExpandIconSelectorView(view).setImageResource(
+                        R.drawable.restore_tabs_expand_more);
+                getSelectedDeviceView(view).setOnClickListener((v) -> delegate.onShowDeviceList());
+            } else {
+                getExpandIconSelectorView(view).setVisibility(View.GONE);
+                getSelectedDeviceView(view).setOnClickListener(null);
+            }
 
             int numSelectedTabs =
                     model.get(REVIEW_TABS_MODEL_LIST).size() - model.get(NUM_TABS_DESELECTED);
@@ -109,6 +100,12 @@
             return;
         }
 
+        if (session.formFactor == FormFactor.PHONE) {
+            getDeviceIconView(view).setImageResource(R.drawable.restore_tabs_phone_icon);
+        } else if (session.formFactor == FormFactor.TABLET) {
+            getDeviceIconView(view).setImageResource(R.drawable.restore_tabs_tablet_icon);
+        }
+
         getDeviceNameTextView(view).setText(session.name);
         CharSequence lastModifiedTimeString = DateUtils.getRelativeTimeSpanString(
                 session.modifiedTime, System.currentTimeMillis(), 0);
@@ -119,13 +116,6 @@
         getSessionInfoTextView(view).setText(sessionInfo);
     }
 
-    private static void allKeysBinder(
-            PropertyModel model, ViewHolder view, BindScreenCallback callback) {
-        for (PropertyKey propertyKey : ALL_KEYS) {
-            callback.bind(model, view, propertyKey);
-        }
-    }
-
     private static TextView getDeviceNameTextView(ViewHolder view) {
         return view.mContentView.findViewById(R.id.restore_tabs_promo_sheet_device_name);
     }
@@ -135,7 +125,7 @@
     }
 
     private static LinearLayout getSelectedDeviceView(ViewHolder view) {
-        return view.mContentView.findViewById(R.id.selected_device_view);
+        return view.mContentView.findViewById(R.id.restore_tabs_selected_device_view);
     }
 
     private static ButtonCompat getRestoreTabsButton(ViewHolder view) {
@@ -145,4 +135,12 @@
     private static ButtonCompat getReviewTabsButton(ViewHolder view) {
         return view.mContentView.findViewById(R.id.restore_tabs_button_review_tabs);
     }
+
+    private static ImageView getExpandIconSelectorView(ViewHolder view) {
+        return view.mContentView.findViewById(R.id.restore_tabs_expand_icon_device_selection);
+    }
+
+    private static ImageView getDeviceIconView(ViewHolder view) {
+        return view.mContentView.findViewById(R.id.restore_tabs_promo_sheet_device_icon);
+    }
 }
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinderUnitTest.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinderUnitTest.java
index 678ce1d..89d4241 100644
--- a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinderUnitTest.java
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsPromoScreenViewBinderUnitTest.java
@@ -14,13 +14,14 @@
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.NUM_TABS_DESELECTED;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.REVIEW_TABS_MODEL_LIST;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.SELECTED_DEVICE;
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.DEVICE_SCREEN;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ScreenType.HOME_SCREEN;
 import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.VISIBLE;
 
 import android.app.Activity;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.junit.Assert;
@@ -37,6 +38,7 @@
 import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper.ForeignSessionTab;
 import org.chromium.chrome.browser.recent_tabs.R;
 import org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.DetailItemType;
+import org.chromium.components.sync_device_info.FormFactor;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -53,7 +55,6 @@
     private RestoreTabsPromoScreenCoordinator.Delegate mMockDelegate;
 
     private Activity mActivity;
-    private ViewGroup mParentView;
     private View mRestoreTabsPromoView;
     private PropertyModel mModel;
     private PropertyModelChangeProcessor mPropertyModelChangeProcessor;
@@ -81,7 +82,8 @@
     public void testOnHomeScreen_setSelectedDevice() {
         mModel.set(CURRENT_SCREEN, HOME_SCREEN);
         mModel.set(SELECTED_DEVICE,
-                new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>()));
+                new ForeignSession(
+                        "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE));
         TextView deviceNameView =
                 mRestoreTabsPromoView.findViewById(R.id.restore_tabs_promo_sheet_device_name);
         TextView deviceInfoView =
@@ -94,7 +96,8 @@
     public void testSetHomeScreen() {
         mModel.set(HOME_SCREEN_DELEGATE, mMockDelegate);
         mModel.set(SELECTED_DEVICE,
-                new ForeignSession("tag", "John's iPhone 6", 32L, new ArrayList<>()));
+                new ForeignSession(
+                        "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE));
         mModel.set(CURRENT_SCREEN, HOME_SCREEN);
 
         TextView deviceNameView =
@@ -104,7 +107,8 @@
         Assert.assertEquals("John's iPhone 6", deviceNameView.getText());
         Assert.assertEquals("0 tabs, last updated: Jan 1, 1970", deviceInfoView.getText());
 
-        View sessionInfoLayout = mRestoreTabsPromoView.findViewById(R.id.selected_device_view);
+        View sessionInfoLayout =
+                mRestoreTabsPromoView.findViewById(R.id.restore_tabs_selected_device_view);
         Assert.assertNotNull(sessionInfoLayout);
         sessionInfoLayout.performClick();
         verify(mMockDelegate, times(1)).onShowDeviceList();
@@ -179,4 +183,56 @@
                 mRestoreTabsPromoView.findViewById(R.id.restore_tabs_button_open_tabs);
         Assert.assertEquals("Open 1 tab", openTabsButtonText.getText());
     }
+
+    @Test
+    public void testSetHomeScreen_oneSessionInModelList() {
+        mModel.set(HOME_SCREEN_DELEGATE, mMockDelegate);
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPhone 6", 32L, new ArrayList<>(), FormFactor.PHONE);
+        ModelList deviceItems = mModel.get(DEVICE_MODEL_LIST);
+        PropertyModel model = ForeignSessionItemProperties.create(
+                /*session=*/session, /*isSelected=*/true,
+                /*onClickListener=*/() -> { mModel.set(CURRENT_SCREEN, DEVICE_SCREEN); });
+        deviceItems.add(new ListItem(DetailItemType.DEVICE, model));
+
+        ImageView expandSelectorView1 =
+                mRestoreTabsPromoView.findViewById(R.id.restore_tabs_expand_icon_device_selection);
+        Assert.assertEquals(View.VISIBLE, expandSelectorView1.getVisibility());
+
+        mModel.set(CURRENT_SCREEN, HOME_SCREEN);
+
+        ImageView expandSelectorView2 =
+                mRestoreTabsPromoView.findViewById(R.id.restore_tabs_expand_icon_device_selection);
+        Assert.assertEquals(View.GONE, expandSelectorView2.getVisibility());
+
+        View sessionInfoLayout =
+                mRestoreTabsPromoView.findViewById(R.id.restore_tabs_selected_device_view);
+        Assert.assertNotNull(sessionInfoLayout);
+        sessionInfoLayout.performClick();
+        Assert.assertNotEquals(DEVICE_SCREEN, mModel.get(CURRENT_SCREEN));
+    }
+
+    @Test
+    public void testSetHomeScreen_sessionIsTablet() {
+        mModel.set(HOME_SCREEN_DELEGATE, mMockDelegate);
+        ForeignSession session = new ForeignSession(
+                "tag", "John's iPad Air", 32L, new ArrayList<>(), FormFactor.TABLET);
+        ModelList deviceItems = mModel.get(DEVICE_MODEL_LIST);
+        PropertyModel model = ForeignSessionItemProperties.create(
+                /*session=*/session, /*isSelected=*/true,
+                /*onClickListener=*/() -> { mModel.set(CURRENT_SCREEN, DEVICE_SCREEN); });
+        deviceItems.add(new ListItem(DetailItemType.DEVICE, model));
+        mModel.set(CURRENT_SCREEN, HOME_SCREEN);
+
+        View sessionInfoLayout =
+                mRestoreTabsPromoView.findViewById(R.id.restore_tabs_selected_device_view);
+        Assert.assertNotNull(sessionInfoLayout);
+        sessionInfoLayout.performClick();
+        Assert.assertNotEquals(DEVICE_SCREEN, mModel.get(CURRENT_SCREEN));
+
+        mModel.set(SELECTED_DEVICE, session);
+        ImageView deviceIconView =
+                mRestoreTabsPromoView.findViewById(R.id.restore_tabs_promo_sheet_device_icon);
+        Assert.assertNotNull(deviceIconView.getDrawable());
+    }
 }
diff --git a/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsViewBinderHelper.java b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsViewBinderHelper.java
new file mode 100644
index 0000000..06a4ca8
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/src/org/chromium/chrome/browser/recent_tabs/ui/RestoreTabsViewBinderHelper.java
@@ -0,0 +1,37 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.recent_tabs.ui;
+
+import static org.chromium.chrome.browser.recent_tabs.RestoreTabsProperties.ALL_KEYS;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * A helper class to rebind all keys on a screen change for the restore tabs workflow.
+ */
+public class RestoreTabsViewBinderHelper {
+    /**
+     * A functional interface to perform a callback and run screen specific bind logic.
+     * @param <T> the view holder that helps bind the screen.
+     */
+    public interface BindScreenCallback<T> {
+        /**
+         * Perform bind logic on all property keys for the respective screen.
+         *
+         * @param model the property model of the screen being handled.
+         * @param view the view holder of the screen being handled.
+         * @param propertyKey the property key being changed.
+         */
+        void bind(PropertyModel model, T view, PropertyKey propertyKey);
+    }
+
+    public static <T> void allKeysBinder(
+            PropertyModel model, T view, BindScreenCallback<T> callback) {
+        for (PropertyKey propertyKey : ALL_KEYS) {
+            callback.bind(model, view, propertyKey);
+        }
+    }
+}
diff --git a/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings.grd b/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings.grd
index 8b9db49..d70e646 100644
--- a/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings.grd
+++ b/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings.grd
@@ -214,6 +214,12 @@
           other {<ph name="TABS_COUNT_MANY">%1$s<ex>8</ex></ph> tabs, last updated: <ph name="TIME_SINCE_MODIFIED">%2$s<ex>30 minutes ago</ex></ph>}
         }
       </message>
+      <message name="IDS_RESTORE_TABS_DEVICE_SCREEN_SHEET_TITLE" desc="Title for the Restore Tabs detail sheet where a user can pick a device and navigate back to the promo screen.">
+        Open from
+      </message>
+      <message name="IDS_RESTORE_TABS_BACK_TO_PROMO_SCREEN_ICON_DESCRIPTION" desc="Acessibility string describing the back icon on the device selection and review tabs detail sheets.">
+        Back
+      </message>
     </messages>
   </release>
 </grit>
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings_grd/IDS_RESTORE_TABS_BACK_TO_PROMO_SCREEN_ICON_DESCRIPTION.png.sha1 b/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings_grd/IDS_RESTORE_TABS_BACK_TO_PROMO_SCREEN_ICON_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..4bc8a0a
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings_grd/IDS_RESTORE_TABS_BACK_TO_PROMO_SCREEN_ICON_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+cdb58a6540450bba931a23cbd78a2e24603b99d2
\ No newline at end of file
diff --git a/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings_grd/IDS_RESTORE_TABS_DEVICE_SCREEN_SHEET_TITLE.png.sha1 b/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings_grd/IDS_RESTORE_TABS_DEVICE_SCREEN_SHEET_TITLE.png.sha1
new file mode 100644
index 0000000..4bc8a0a
--- /dev/null
+++ b/chrome/browser/recent_tabs/internal/android/java/strings/android_restore_tabs_strings_grd/IDS_RESTORE_TABS_DEVICE_SCREEN_SHEET_TITLE.png.sha1
@@ -0,0 +1 @@
+cdb58a6540450bba931a23cbd78a2e24603b99d2
\ No newline at end of file
diff --git a/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.cc b/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.cc
index 80745f4a..c7963ac 100644
--- a/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.cc
+++ b/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/accessibility/accessibility_state_utils.h"
 #include "chrome/browser/accessibility/pdf_ocr_controller.h"
@@ -27,6 +28,11 @@
          features::IsPdfOcrEnabled();
 }
 
+void RecordUserSelection(PdfOcrUserSelection user_selection) {
+  base::UmaHistogramEnumeration("Accessibility.PdfOcr.UserSelection",
+                                user_selection);
+}
+
 }  // namespace
 
 PdfOcrMenuObserver::PdfOcrMenuObserver(RenderViewContextMenuProxy* proxy)
@@ -87,6 +93,7 @@
       VLOG(2) << "Turning off PDF OCR from the context menu";
       profile->GetPrefs()->SetBoolean(prefs::kAccessibilityPdfOcrAlwaysActive,
                                       false);
+      RecordUserSelection(PdfOcrUserSelection::kTurnOffFromContextMenu);
       break;
     case IDC_CONTENT_CONTEXT_PDF_OCR_ALWAYS:
       // When a user choose "Always" to run the PDF OCR, we save this
@@ -95,12 +102,14 @@
         VLOG(2) << "Setting PDF OCR to be always active from the context menu";
         profile->GetPrefs()->SetBoolean(prefs::kAccessibilityPdfOcrAlwaysActive,
                                         true);
+        RecordUserSelection(PdfOcrUserSelection::kTurnOnAlwaysFromContextMenu);
       }
       break;
     case IDC_CONTENT_CONTEXT_PDF_OCR_ONCE:
       VLOG(2) << "Running PDF OCR only once from the context menu";
       screen_ai::PdfOcrControllerFactory::GetForProfile(profile)
           ->RunPdfOcrOnlyOnce(proxy_->GetWebContents());
+      RecordUserSelection(PdfOcrUserSelection::kTurnOnOnceFromContextMenu);
       break;
     default:
       NOTREACHED();
diff --git a/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h b/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h
index 7972b31..d5d8b61a 100644
--- a/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h
+++ b/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h
@@ -13,6 +13,20 @@
 
 class RenderViewContextMenuProxy;
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. Must be kept in sync with
+// PdfOcrUserSelection enum in //tools/metrics/histograms/enums.xml.
+enum class PdfOcrUserSelection {
+  kTurnOnOnceFromContextMenu = 0,
+  kTurnOnAlwaysFromContextMenu = 1,
+  kTurnOffFromContextMenu = 2,
+  kTurnOnAlwaysFromMoreActions = 3,
+  kTurnOffFromMoreActions = 4,
+  kTurnOnAlwaysFromSettings = 5,
+  kTurnOffFromSettings = 6,
+  kMaxValue = kTurnOffFromSettings,
+};
+
 // An observer that listens to events from the RenderViewContextMenu class and
 // shows the PDF OCR menu if a screen reader is enabled.
 class PdfOcrMenuObserver : public RenderViewContextMenuObserver {
diff --git a/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer_browsertest.cc b/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer_browsertest.cc
index 106aec19..1ccbe00 100644
--- a/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/pdf_ocr_menu_observer_browsertest.cc
@@ -4,11 +4,13 @@
 
 #include "chrome/browser/renderer_context_menu/pdf_ocr_menu_observer.h"
 
+#include "base/test/metrics/histogram_tester.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/renderer_context_menu/mock_render_view_context_menu.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "components/metrics/content/subprocess_metrics_provider.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/context_menu_params.h"
 #include "content/public/test/browser_test.h"
@@ -131,3 +133,97 @@
   EXPECT_TRUE(item.checked);
   EXPECT_FALSE(item.hidden);
 }
+
+IN_PROC_BROWSER_TEST_F(PdfOcrMenuObserverTest,
+                       CheckUmaWhenTurnOnPdfOcrAlwaysFromContextMenu) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Enable Chromevox.
+  ash::AccessibilityManager::Get()->EnableSpokenFeedback(true);
+#else
+  // Spoof a screen reader.
+  content::testing::ScopedContentAXModeSetter scoped_accessibility_mode(
+      ui::AXMode::kScreenReader);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  menu()->GetPrefs()->SetBoolean(prefs::kAccessibilityPdfOcrAlwaysActive,
+                                 false);
+  InitMenu();
+  ASSERT_EQ(menu()->GetMenuSize(), 3u);
+
+  // Get the PDF OCR always menu item.
+  MockRenderViewContextMenu::MockMenuItem item;
+  menu()->GetMenuItem(1, &item);
+  ASSERT_EQ(IDC_CONTENT_CONTEXT_PDF_OCR_ALWAYS, item.command_id);
+
+  // Turn on PDF OCR always.
+  base::HistogramTester histograms;
+  menu()->ExecuteCommand(item.command_id, 0);
+
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  histograms.ExpectUniqueSample(
+      "Accessibility.PdfOcr.UserSelection",
+      PdfOcrUserSelection::kTurnOnAlwaysFromContextMenu,
+      /*expected_bucket_count=*/1);
+}
+
+IN_PROC_BROWSER_TEST_F(PdfOcrMenuObserverTest,
+                       CheckUmaWhenTurnOffPdfOcrFromContextMenu) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Enable Chromevox.
+  ash::AccessibilityManager::Get()->EnableSpokenFeedback(true);
+#else
+  // Spoof a screen reader.
+  content::testing::ScopedContentAXModeSetter scoped_accessibility_mode(
+      ui::AXMode::kScreenReader);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  menu()->GetPrefs()->SetBoolean(prefs::kAccessibilityPdfOcrAlwaysActive, true);
+  InitMenu();
+  ASSERT_EQ(menu()->GetMenuSize(), 1u);
+
+  // Get the PDF OCR checked menu item.
+  MockRenderViewContextMenu::MockMenuItem item;
+  menu()->GetMenuItem(0, &item);
+  ASSERT_EQ(IDC_CONTENT_CONTEXT_PDF_OCR, item.command_id);
+  ASSERT_TRUE(item.checked);
+
+  // Turn off PDF OCR.
+  base::HistogramTester histograms;
+  menu()->ExecuteCommand(item.command_id, 0);
+
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  histograms.ExpectUniqueSample("Accessibility.PdfOcr.UserSelection",
+                                PdfOcrUserSelection::kTurnOffFromContextMenu,
+                                /*expected_bucket_count=*/1);
+}
+
+IN_PROC_BROWSER_TEST_F(PdfOcrMenuObserverTest,
+                       CheckUmaWhenTurnOnPdfOcrOnceFromContextMenu) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Enable Chromevox.
+  ash::AccessibilityManager::Get()->EnableSpokenFeedback(true);
+#else
+  // Spoof a screen reader.
+  content::testing::ScopedContentAXModeSetter scoped_accessibility_mode(
+      ui::AXMode::kScreenReader);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  menu()->GetPrefs()->SetBoolean(prefs::kAccessibilityPdfOcrAlwaysActive,
+                                 false);
+  InitMenu();
+  ASSERT_EQ(menu()->GetMenuSize(), 3u);
+
+  // Get the PDF OCR once menu item.
+  MockRenderViewContextMenu::MockMenuItem item;
+  menu()->GetMenuItem(2, &item);
+  ASSERT_EQ(IDC_CONTENT_CONTEXT_PDF_OCR_ONCE, item.command_id);
+
+  // Run PDF OCR once.
+  base::HistogramTester histograms;
+  menu()->ExecuteCommand(item.command_id, 0);
+
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  histograms.ExpectUniqueSample("Accessibility.PdfOcr.UserSelection",
+                                PdfOcrUserSelection::kTurnOnOnceFromContextMenu,
+                                /*expected_bucket_count=*/1);
+}
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index b8fa08a2..36fbb19 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -2140,8 +2140,16 @@
   }
 };
 
+// https://crbug.com/1444953
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_ValidLensRegionSearchWithUnifiedSidePanel \
+  DISABLED_ValidLensRegionSearchWithUnifiedSidePanel
+#else
+#define MAYBE_ValidLensRegionSearchWithUnifiedSidePanel \
+  ValidLensRegionSearchWithUnifiedSidePanel
+#endif
 IN_PROC_BROWSER_TEST_F(SearchByRegionWithUnifiedSidePanelBrowserTest,
-                       ValidLensRegionSearchWithUnifiedSidePanel) {
+                       MAYBE_ValidLensRegionSearchWithUnifiedSidePanel) {
   SetupUnifiedSidePanel();
   // We need a base::RunLoop to ensure that our test does not finish until the
   // side panel has opened and we have verified the URL.
@@ -2185,8 +2193,17 @@
                                    ".*ep=crs&re=df&s=4&st=\\d+&lm=.+"));
 }
 
-IN_PROC_BROWSER_TEST_F(SearchByRegionWithUnifiedSidePanelBrowserTest,
-                       ValidFullscreenLensRegionSearchWithUnifiedSidePanel) {
+// https://crbug.com/1444953
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_ValidFullscreenLensRegionSearchWithUnifiedSidePanel \
+  DISABLED_ValidFullscreenLensRegionSearchWithUnifiedSidePanel
+#else
+#define MAYBE_ValidFullscreenLensRegionSearchWithUnifiedSidePanel \
+  ValidFullscreenLensRegionSearchWithUnifiedSidePanel
+#endif
+IN_PROC_BROWSER_TEST_F(
+    SearchByRegionWithUnifiedSidePanelBrowserTest,
+    MAYBE_ValidFullscreenLensRegionSearchWithUnifiedSidePanel) {
   SetupUnifiedSidePanel();
   // We need a base::RunLoop to ensure that our test does not finish until the
   // side panel has opened and we have verified the URL.
diff --git a/chrome/browser/resources/chromeos/login/display_manager.js b/chrome/browser/resources/chromeos/login/display_manager.js
index 3768b73..78675a2 100644
--- a/chrome/browser/resources/chromeos/login/display_manager.js
+++ b/chrome/browser/resources/chromeos/login/display_manager.js
@@ -19,10 +19,16 @@
  * Maximum time in milliseconds to wait for step transition to finish.
  * The value is used as the duration for ensureTransitionEndEvent below.
  * It needs to be inline with the step screen transition duration time
- * defined in css file. The current value in css is 200ms. To avoid emulated
- * transitionend fired before real one, 250ms is used.
+ * defined in css file. The current value in css is 1,150ms. To avoid emulated
+ * transitionend fired before real one, +50ms is used.
  */
-const MAX_SCREEN_TRANSITION_DURATION = 250;
+const MAX_SCREEN_TRANSITION_DURATION = 1200;
+
+/**
+ * Maximum delay to call triggerDown from cpp logic. If the logic fails,
+ * triggerDown should be called after this duration to unblock CUJ.
+ */
+const TRIGGERDOWN_FALLBACK_DELAY = 10000;
 
 /**
  * As Polymer behaviors do not provide true inheritance, when two behaviors
@@ -264,18 +270,14 @@
         }
       } else {
         // First screen on OOBE launch.
+        const isOobeSimonEnabled =
+            loadTimeData.getBoolean('isOobeSimonEnabled');
         if (this.isOobeUI() && innerContainer.classList.contains('down')) {
-          innerContainer.classList.remove('down');
-          innerContainer.addEventListener('transitionend', function f(e) {
-            innerContainer.removeEventListener('transitionend', f);
-            // Refresh defaultControl. It could have changed.
-            const defaultControl = newStep.defaultControl;
-            if (defaultControl) {
-              defaultControl.focus();
-            }
-          });
-          ensureTransitionEndEvent(
-              innerContainer, MAX_SCREEN_TRANSITION_DURATION);
+          if (isOobeSimonEnabled) {
+            setTimeout(this.triggerDown.bind(this), TRIGGERDOWN_FALLBACK_DELAY);
+          } else {
+            this.triggerDown();
+          }
         } else {
           if (defaultControl) {
             defaultControl.focus();
@@ -396,6 +398,29 @@
       }
     }
 
+    /**
+     * Trigger of play down animation for current screen step.
+     */
+    triggerDown() {
+      const innerContainer = $('inner-container');
+      if (!this.isOobeUI() || !innerContainer.classList.contains('down')) {
+        return;
+      }
+
+      innerContainer.classList.remove('down');
+      innerContainer.addEventListener('transitionend', function f(e) {
+        innerContainer.removeEventListener('transitionend', f);
+        // Refresh defaultControl. It could have changed.
+        const stepId = this.screens_[this.currentStep_];
+        const step = $(stepId);
+        const defaultControl = step.defaultControl;
+        if (defaultControl) {
+          defaultControl.focus();
+        }
+      }.bind(this));
+      ensureTransitionEndEvent(innerContainer, MAX_SCREEN_TRANSITION_DURATION);
+    }
+
     /** Initializes demo mode start listener.
      * @suppress {missingProperties}
      * currentScreen.onSetupDemoModeGesture()
diff --git a/chrome/browser/resources/chromeos/login/oobe.css b/chrome/browser/resources/chromeos/login/oobe.css
index 02390f3..d87f9ed 100644
--- a/chrome/browser/resources/chromeos/login/oobe.css
+++ b/chrome/browser/resources/chromeos/login/oobe.css
@@ -159,12 +159,21 @@
   right: 0;
   top: 0;
 }
-
+/* Animation to fade in shelf for simon-enabled */
+:root.simon-enabled #scroll-container:has(#oobe.connect
+#inner-container:not(.down)) {
+  transition: bottom 900ms cubic-bezier(0.20, 0.00, 0.00, 1.00);
+}
+:root.simon-enabled #scroll-container:has(#oobe.connect
+#inner-container.down) {
+  bottom: 0;
+}
 #scroll-container.disable-scroll {
   overflow-y: hidden;
 }
 
 #outer-container {
+  display: flex;
   height: 100%;
   z-index: 1;
 }
@@ -189,6 +198,7 @@
   border-radius: var(--oobe-container-border-radius);
   box-shadow: var(--oobe-container-elevation);
   display: grid;
+  overflow: hidden;
   place-items: center;
   position: relative;
 }
@@ -207,14 +217,58 @@
   position: absolute;
 }
 
+#welcome-backdrop {
+  background-color: var(--oobe-bg-color);
+  background-position: center;
+  background-size: cover;
+  display: none;
+  inset: 0;
+  position: absolute;
+  z-index: -1;
+}
+:root.simon-enabled[dir=rtl] #oobe.connect #welcome-backdrop {
+  transform: scaleX(-100%);
+}
+:root.simon-enabled #oobe.connect #welcome-backdrop {
+  display: block;
+}
+:root.simon-enabled #oobe.connect #inner-container {
+  background-color: transparent;
+}
+
 /* Only play this animation when 'down' class is removed. */
 .oobe-display #inner-container:not(.down) {
   transition: transform 200ms ease-in-out;
 }
+:root.simon-enabled #oobe.connect #inner-container:not(.down) {
+  min-height: var(--oobe-adaptive-dialog-height);
+  min-width: var(--oobe-adaptive-dialog-width);
+  /* Property duration curve delay */
+  transition: border-radius 900ms cubic-bezier(0.20, 0.00, 0.00, 1.00),
+              min-height 900ms cubic-bezier(0.20, 0.00, 0.00, 1.00),
+              min-width 900ms cubic-bezier(0.20, 0.00, 0.00, 1.00);
+}
+:root.simon-enabled #oobe.connect #inner-container #connect {
+  /* Property duration curve delay */
+  transition: opacity 250ms linear 900ms;
+}
 
 .oobe-display #inner-container.down {
   transform: translateY(50px) rotateX(-2.5deg);
 }
+:root.simon-enabled #oobe.connect #inner-container.down {
+  border-radius: 0;
+  min-height: 100vh;
+  min-width: 100vw;
+  transform: translateY(0) rotateX(0);
+}
+:root.simon-enabled [orientation=vertical] #oobe.connect #inner-container.down {
+  min-height: 100vw;
+  min-width: 100vh;
+}
+:root.simon-enabled #oobe.connect #inner-container.down #connect {
+  opacity: 0;
+}
 
 /* Styles related to the `top_header_bar` */
 
diff --git a/chrome/browser/resources/chromeos/login/oobe.html b/chrome/browser/resources/chromeos/login/oobe.html
index 239f7da9..d87550f 100644
--- a/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chrome/browser/resources/chromeos/login/oobe.html
@@ -37,6 +37,9 @@
       <div class="oobe-vertical-margin"></div>
       <div id="oobe" class="faded">
         <div id="inner-container" class="down">
+          <if expr="_google_chrome">
+            <div id="welcome-backdrop" aria-hidden="true"></div>
+          </if>
           <!-- SCREENS ARE ADDED HERE DYNAMICALLY -->
         </div>
       </div>
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index ac4152c..e0053b45a 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -54,6 +54,26 @@
   window.Oobe = Oobe;
 }
 
+// Load Simon backdrop asset and trigger event once backdrop is ready to show.
+function loadSimonBackdrop() {
+  const backdropContainer = $('welcome-backdrop');
+  if (!backdropContainer || backdropContainer.style.backgroundImage !== '') {
+    return;
+  }
+
+  // Load of background-img can't be observed by js, therefore load to img
+  // first. BackdropContainer will take the file from cache instead of
+  // downloading it again.
+  const srcUrl = 'internal_assets/welcome_backdrop.svg';
+  var img = new Image();
+  img.addEventListener('load', function f(e) {
+    img.removeEventListener('load', f);
+    backdropContainer.style.backgroundImage = 'url(' + srcUrl + ')';
+    chrome.send('backdropLoaded');
+  });
+  img.src = srcUrl;
+}
+
 function initializeOobe() {
   if (document.readyState === 'loading') {
     return;
@@ -61,6 +81,12 @@
   document.removeEventListener('DOMContentLoaded', initializeOobe);
   traceExecution(TraceEvent.DOM_CONTENT_LOADED);
 
+  // Prepare Simon flow backdrop.
+  const isOobeSimonEnabled = loadTimeData.getBoolean('isOobeSimonEnabled');
+  if (isOobeSimonEnabled) {
+    loadSimonBackdrop();
+  }
+
   const isOobeJellyEnabled = loadTimeData.getBoolean('isOobeJellyEnabled');
   if (isOobeJellyEnabled) {
     // Start listening for color changes in 'chrome://theme/colors.css'. Force
diff --git a/chrome/browser/resources/downloads/toolbar.html b/chrome/browser/resources/downloads/toolbar.html
index 1d217cd..7c6ee98 100644
--- a/chrome/browser/resources/downloads/toolbar.html
+++ b/chrome/browser/resources/downloads/toolbar.html
@@ -7,6 +7,7 @@
 
   #toolbar {
     flex: 1;
+    min-width: 0;
   }
 
   cr-icon-button {
diff --git a/chrome/browser/resources/new_tab_page/lazy_load.ts b/chrome/browser/resources/new_tab_page/lazy_load.ts
index 44e1d7f3..ce6dda7 100644
--- a/chrome/browser/resources/new_tab_page/lazy_load.ts
+++ b/chrome/browser/resources/new_tab_page/lazy_load.ts
@@ -42,7 +42,7 @@
 export {HistoryClusterElementType, HistoryClusterImageDisplayState, HistoryClusterLayoutType, historyClustersDescriptor, HistoryClustersModuleElement, LAYOUT_1_MIN_IMAGE_VISITS, LAYOUT_1_MIN_VISITS, LAYOUT_2_MIN_IMAGE_VISITS, LAYOUT_2_MIN_VISITS, LAYOUT_3_MIN_IMAGE_VISITS, LAYOUT_3_MIN_VISITS} from './modules/history_clusters/module.js';
 export {SuggestTileModuleElement} from './modules/history_clusters/suggest_tile.js';
 export {TileModuleElement} from './modules/history_clusters/tile.js';
-export {HistoryClustersModuleElement as HistoryClustersV2ModuleElement, historyClustersV2Descriptor} from './modules/history_clusters_v2/module.js';
+export {historyClustersDescriptor as historyClustersV2Descriptor, HistoryClustersModuleElement as HistoryClustersV2ModuleElement} from './modules/history_clusters_v2/module.js';
 export {InfoDialogElement} from './modules/info_dialog.js';
 export {InitializeModuleCallback, Module, ModuleDescriptor} from './modules/module_descriptor.js';
 export {counterfactualLoad} from './modules/module_descriptors.js';
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters/module.html b/chrome/browser/resources/new_tab_page/modules/history_clusters/module.html
index ef6201f..4a4d070 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters/module.html
@@ -113,7 +113,8 @@
       </ntp-history-clusters-tile>
       <template is="dom-if" if="[[shouldShowCartTile_(cart)]]" restamp>
         <ntp-history-clusters-cart-tile
-            id="cartTile" cart="[[cart]]" class="secondary-tile">
+            id="cartTile" cart="[[cart]]" class="secondary-tile"
+            on-click="onCartTileClick_" on-aux-click="onCartTileClick_">
         </ntp-history-clusters-cart-tile>
       </template>
       <template is="dom-if" if="[[!shouldShowCartTile_(cart)]]" restamp>
@@ -136,7 +137,8 @@
       </ntp-history-clusters-tile>
       <template is="dom-if" if="[[shouldShowCartTile_(cart)]]" restamp>
         <ntp-history-clusters-cart-tile
-            id="cartTile" cart="[[cart]]" class="secondary-tile">
+            id="cartTile" cart="[[cart]]" class="secondary-tile"
+            on-click="onCartTileClick_" on-aux-click="onCartTileClick_">
         </ntp-history-clusters-cart-tile>
       </template>
       <template is="dom-if" if="[[!shouldShowCartTile_(cart)]]" restamp>
@@ -167,7 +169,8 @@
       </div>
       <template is="dom-if" if="[[shouldShowCartTile_(cart)]]" restamp>
         <ntp-history-clusters-cart-tile
-            id="cartTile" cart="[[cart]]" class="secondary-tile">
+            id="cartTile" cart="[[cart]]" class="secondary-tile"
+            on-click="onCartTileClick_" on-aux-click="onCartTileClick_">
         </ntp-history-clusters-cart-tile>
       </template>
       <template is="dom-if" if="[[!shouldShowCartTile_(cart)]]" restamp>
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters/module.ts b/chrome/browser/resources/new_tab_page/modules/history_clusters/module.ts
index ea133db..2566b9e 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters/module.ts
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters/module.ts
@@ -49,6 +49,7 @@
   VISIT = 0,
   SUGGEST = 1,
   SHOW_ALL = 2,
+  CART = 3,
 }
 
 /**
@@ -165,6 +166,10 @@
     this.recordClick_(HistoryClusterElementType.SUGGEST);
   }
 
+  private onCartTileClick_() {
+    this.recordClick_(HistoryClusterElementType.CART);
+  }
+
   private recordClick_(type: HistoryClusterElementType) {
     chrome.metricsPrivate.recordEnumerationValue(
         `NewTabPage.HistoryClusters.Layout${this.layoutType}.Click`, type,
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts
index f4af30c..150720e5 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts
@@ -16,6 +16,8 @@
 
 import {getTemplate} from './module.html.js';
 
+export const MAX_MODULE_ELEMENT_INSTANCES = 3;
+
 export interface HistoryClustersModuleElement {
   $: {
     infoDialogRender: CrLazyRenderElement<InfoDialogElement>,
@@ -99,20 +101,31 @@
 customElements.define(
     HistoryClustersModuleElement.is, HistoryClustersModuleElement);
 
-async function createElement(): Promise<HTMLElement> {
-  const {clusters} =
-      await HistoryClustersProxyImpl.getInstance().handler.getClusters();
-  // Do not show module if there are no clusters.
-  if (clusters.length === 0) {
-    return document.createElement('div');
-  }
-
+async function createElement(cluster: Cluster):
+    Promise<HistoryClustersModuleElement> {
   const element = new HistoryClustersModuleElement();
-  element.cluster = clusters[0];
+  element.cluster = cluster;
 
-  return element as HTMLElement;
+  return element;
 }
 
-export const historyClustersV2Descriptor: ModuleDescriptor =
-    new ModuleDescriptor(
-        /*id=*/ 'history_clusters', createElement);
+async function createElements(): Promise<HTMLElement[]|null> {
+  const {clusters} =
+      await HistoryClustersProxyImpl.getInstance().handler.getClusters();
+  if (!clusters || clusters.length === 0) {
+    return null;
+  }
+
+  const elements: HistoryClustersModuleElement[] = [];
+  for (let i = 0; i < clusters.length; i++) {
+    if (elements.length === MAX_MODULE_ELEMENT_INSTANCES) {
+      break;
+    }
+    elements.push(await createElement(clusters[i]));
+  }
+
+  return (elements as unknown) as HTMLElement[];
+}
+
+export const historyClustersDescriptor: ModuleDescriptor = new ModuleDescriptor(
+    /*id=*/ 'history_clusters', createElements);
diff --git a/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts b/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
index ef70983..f60b3cc4 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
+++ b/chrome/browser/resources/new_tab_page/modules/module_descriptors.ts
@@ -18,7 +18,7 @@
 import {feedDescriptor} from './feed/module.js';
 import {HistoryClustersProxyImpl} from './history_clusters/history_clusters_proxy.js';
 import {historyClustersDescriptor} from './history_clusters/module.js';
-import {historyClustersV2Descriptor} from './history_clusters_v2/module.js';
+import {historyClustersDescriptor as historyClustersV2Descriptor} from './history_clusters_v2/module.js';
 import {ModuleDescriptor} from './module_descriptor.js';
 import {ModuleRegistry} from './module_registry.js';
 import {photosDescriptor} from './photos/module.js';
diff --git a/chrome/browser/resources/pdf/constants.ts b/chrome/browser/resources/pdf/constants.ts
index 9969a4771..549b80308 100644
--- a/chrome/browser/resources/pdf/constants.ts
+++ b/chrome/browser/resources/pdf/constants.ts
@@ -70,3 +70,19 @@
   fromScriptingAPI?: boolean,
   fromPlugin?: boolean,
 };
+
+/**
+ * These values are persisted to logs. Entries should not be renumbered and
+ * numeric values should never be reused. This enum is tied directly to a UMA
+ * enum, PdfOcrUserSelection, defined in //tools/metrics/histograms/enums.xml
+ * and should always reflect it (do not change one without changing the other).
+ */
+export enum PdfOcrUserSelection {
+  TURN_ON_ONCE_FROM_CONTEXT_MENU = 0,
+  TURN_ON_ALWAYS_FROM_CONTEXT_MENU = 1,
+  TURN_OFF_FROM_CONTEXT_MENU = 2,
+  TURN_ON_ALWAYS_FROM_MORE_ACTIONS = 3,
+  TURN_OFF_FROM_MORE_ACTIONS = 4,
+  TURN_ON_ALWAYS_FROM_SETTINGS = 5,
+  TURN_OFF_FROM_SETTINGS = 6,
+}
diff --git a/chrome/browser/resources/pdf/elements/viewer-toolbar.ts b/chrome/browser/resources/pdf/elements/viewer-toolbar.ts
index 4a3c978..404ff79 100644
--- a/chrome/browser/resources/pdf/elements/viewer-toolbar.ts
+++ b/chrome/browser/resources/pdf/elements/viewer-toolbar.ts
@@ -22,7 +22,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {FittingType} from '../constants.js';
-import {record, UserAction} from '../metrics.js';
+import {record, recordPdfOcrUserSelection, UserAction} from '../metrics.js';
 // <if expr="enable_screen_ai_service">
 import {PdfOcrPrefCallback, PdfViewerPrivateProxyImpl} from '../pdf_viewer_private_proxy.js';
 
@@ -402,7 +402,7 @@
         await PdfViewerPrivateProxyImpl.getInstance().setPdfOcrPref(valueToSet);
     if (success) {
       this.pdfOcrAlwaysActive_ = valueToSet;
-      // TODO(crbug.com/1393069): Start/stop PDF OCR accordingly.
+      recordPdfOcrUserSelection(this.pdfOcrAlwaysActive_);
     }
   }
 
diff --git a/chrome/browser/resources/pdf/metrics.ts b/chrome/browser/resources/pdf/metrics.ts
index 47cd4f17..88d1f8f 100644
--- a/chrome/browser/resources/pdf/metrics.ts
+++ b/chrome/browser/resources/pdf/metrics.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {FittingType} from './constants.js';
+import {FittingType, PdfOcrUserSelection} from './constants.js';
 
 // Handles events specific to the PDF viewer and logs the corresponding metrics.
 
@@ -44,6 +44,29 @@
   }
 }
 
+/**
+ * Records when the user selects to turn on or off PDF OCR.
+ * @param userSelection the new UserSelection.
+ */
+export function recordPdfOcrUserSelection(pdfOcrAlwaysActive: boolean) {
+  // Need to divide Object.keys().length by 2 to get the enum size due to enum
+  // reverse mapping in TypeScript.
+  const enumSize = Object.keys(PdfOcrUserSelection).length / 2;
+  const enumValue = pdfOcrAlwaysActive ?
+      PdfOcrUserSelection.TURN_ON_ALWAYS_FROM_MORE_ACTIONS :
+      PdfOcrUserSelection.TURN_OFF_FROM_MORE_ACTIONS;
+  recordEnumeration('Accessibility.PdfOcr.UserSelection', enumValue, enumSize);
+}
+
+/** Records the given enumeration to chrome.metricsPrivate. */
+export function recordEnumeration(
+    enumKey: string, enumValue: number, enumSize: number) {
+  if (!chrome.metricsPrivate) {
+    return;
+  }
+  chrome.metricsPrivate.recordEnumerationValue(enumKey, enumValue, enumSize);
+}
+
 export function resetForTesting() {
   firstActionRecorded.clear();
   actionsMetric = null;
diff --git a/chrome/browser/resources/settings/chromeos/common/load_time_booleans.ts b/chrome/browser/resources/settings/chromeos/common/load_time_booleans.ts
new file mode 100644
index 0000000..99fafed
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/common/load_time_booleans.ts
@@ -0,0 +1,34 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * Getters for loadTimeData booleans used throughout CrOS Settings.
+ * Export them as functions so they reload the values when overridden in tests.
+ */
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+
+export function isGuest(): boolean {
+  return loadTimeData.getBoolean('isGuest');
+}
+
+export function isAccountManagerEnabled(): boolean {
+  return loadTimeData.getBoolean('isAccountManagerEnabled');
+}
+
+export function isCrostiniAllowed(): boolean {
+  return loadTimeData.getBoolean('isCrostiniAllowed');
+}
+
+export function isCrostiniSupported(): boolean {
+  return loadTimeData.getBoolean('isCrostiniSupported');
+}
+
+export function isKerberosEnabled(): boolean {
+  return loadTimeData.getBoolean('isKerberosEnabled');
+}
+
+export function isPowerwashAllowed(): boolean {
+  return loadTimeData.getBoolean('allowPowerwash');
+}
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html
index 4a4b323..add176a 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html
@@ -9,20 +9,20 @@
       <div class="start">
         $i18n{crostiniPageLabel}
         <div class="secondary" id="secondaryText">
-          <template is="dom-if" if="[[showCrostini]]" restamp>
+          <template is="dom-if" if="[[isCrostiniSupported_]]" restamp>
             <localized-link
                 localized-string="[[i18nAdvanced('crostiniSubtext')]]">
             </localized-link>
           </template>
-          <template is="dom-if" if="[[!showCrostini]]" restamp>
+          <template is="dom-if" if="[[!isCrostiniSupported_]]" restamp>
             <localized-link localized-string="[[i18nAdvanced(
                 'crostiniSubtextNotSupported')]]">
             </localized-link>
           </template>
         </div>
       </div>
-      <template is="dom-if" if="[[showCrostini]]" restamp>
-        <template is="dom-if" if="[[!allowCrostini]]" restamp>
+      <template is="dom-if" if="[[isCrostiniSupported_]]" restamp>
+        <template is="dom-if" if="[[!isCrostiniAllowed_]]" restamp>
           <cr-policy-indicator indicator-type="userPolicy">
           </cr-policy-indicator>
         </template>
@@ -149,4 +149,3 @@
     </os-settings-subpage>
   </template>
 </os-settings-animated-pages>
-
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
index f6023ac..b949480 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
@@ -32,6 +32,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {isCrostiniAllowed, isCrostiniSupported} from '../common/load_time_booleans.js';
 import {DeepLinkingMixin} from '../deep_linking_mixin.js';
 import {Setting} from '../mojom-webui/setting.mojom-webui.js';
 import {routes} from '../os_settings_routes.js';
@@ -113,6 +114,20 @@
         type: Boolean,
       },
 
+      isCrostiniSupported_: {
+        type: Boolean,
+        value: () => {
+          return isCrostiniSupported();
+        },
+      },
+
+      isCrostiniAllowed_: {
+        type: Boolean,
+        value: () => {
+          return isCrostiniAllowed();
+        },
+      },
+
       /**
        * Used by DeepLinkingMixin to focus this page's deep links.
        */
@@ -130,6 +145,8 @@
 
   private browserProxy_: CrostiniBrowserProxy;
   private disableCrostiniInstall_: boolean;
+  private isCrostiniAllowed_: boolean;
+  private isCrostiniSupported_: boolean;
 
   constructor() {
     super();
@@ -140,7 +157,7 @@
   override connectedCallback() {
     super.connectedCallback();
 
-    if (!loadTimeData.getBoolean('allowCrostini')) {
+    if (!this.isCrostiniAllowed_) {
       this.disableCrostiniInstall_ = true;
       return;
     }
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.html b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
index ebcf286..8ed4b948 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
@@ -155,7 +155,6 @@
   <template is="dom-if" route-path="/storage">
     <os-settings-subpage page-title="$i18n{storageTitle}">
       <settings-storage prefs="{{prefs}}"
-          show-crostini="[[showCrostini]]"
           android-enabled="[[androidEnabled_]]">
       </settings-storage>
     </os-settings-subpage>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.ts b/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
index 9aeaed6..cd5ebbb 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
@@ -72,8 +72,6 @@
         notify: true,
       },
 
-      showCrostini: Boolean,
-
       /**
        * |hasMouse_|, |hasPointingStick_|, and |hasTouchpad_| start undefined so
        * observers don't trigger until they have been populated.
diff --git a/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.ts b/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.ts
index 9e1bb756..fb7aeafc63 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.ts
@@ -134,7 +134,7 @@
         return this.i18n('perDeviceKeyboardKeyCommand');
       }
       case MetaKey.kExternalMeta: {
-        return this.i18n('keyboardKeyMeta');
+        return this.i18n('perDeviceKeyboardKeyMeta');
       }
       // Launcher and Search key will display icon instead of text.
       case MetaKey.kLauncher:
diff --git a/chrome/browser/resources/settings/chromeos/device_page/storage.ts b/chrome/browser/resources/settings/chromeos/device_page/storage.ts
index 91d2491..949a789 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/storage.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/storage.ts
@@ -16,6 +16,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {isCrostiniSupported} from '../common/load_time_booleans.js';
 import {routes} from '../os_settings_routes.js';
 import {RouteOriginMixin} from '../route_origin_mixin.js';
 import {Route, Router} from '../router.js';
@@ -66,8 +67,6 @@
         readonly: true,
       },
 
-      showCrostini: Boolean,
-
       isEphemeralUser_: {
         type: Boolean,
         value() {
@@ -91,7 +90,6 @@
     return ['handleCrostiniEnabledChanged_(prefs.crostini.enabled.value)'];
   }
 
-  showCrostini: boolean;
   private browserProxy_: DevicePageBrowserProxy;
   private isEphemeralUser_: boolean;
   private route_: Route;
@@ -310,7 +308,7 @@
    * @param enabled True if Crostini is enabled.
    */
   private handleCrostiniEnabledChanged_(enabled: boolean): void {
-    this.showCrostiniStorage_ = enabled && this.showCrostini;
+    this.showCrostiniStorage_ = enabled && isCrostiniSupported();
   }
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts
index d7654af9..061d5fa 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts
@@ -95,7 +95,11 @@
     this.guid_ = '';
     this.managedProperties_ = undefined;
     this.deviceState_ = null;
-    Router.getInstance().navigateToPreviousRoute();
+
+    // Only navigate backwards if this is page is the current route.
+    if (Router.getInstance().currentRoute === routes.APN) {
+      Router.getInstance().navigateToPreviousRoute();
+    }
   }
 
   override currentRouteChanged(route: Route): void {
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_subpage.ts b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_subpage.ts
index 3c2c0ef0..a456318 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_subpage.ts
@@ -647,7 +647,9 @@
       this.managedProperties_ = undefined;
       this.propertiesReceived_ = false;
 
-      Router.getInstance().navigateToPreviousRoute();
+      if (Router.getInstance().currentRoute === routes.NETWORK_DETAIL) {
+        Router.getInstance().navigateToPreviousRoute();
+      }
     });
   }
 
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.html b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts_subpage.html
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.html
rename to chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts_subpage.html
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts_subpage.ts
similarity index 91%
rename from chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
rename to chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts_subpage.ts
index 54ee355..8843d39 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts_subpage.ts
@@ -4,8 +4,8 @@
 
 /**
  * @fileoverview
- * 'settings-kerberos-accounts' is the settings subpage containing controls to
- * list, add and delete Kerberos Accounts.
+ * 'settings-kerberos-accounts-subpage' is the settings subpage containing
+ * controls to list, add and delete Kerberos Accounts.
  */
 
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
@@ -33,16 +33,16 @@
 import {RouteObserverMixin} from '../route_observer_mixin.js';
 import {Route, Router} from '../router.js';
 
-import {getTemplate} from './kerberos_accounts.html.js';
 import {KerberosAccount, KerberosAccountsBrowserProxy, KerberosAccountsBrowserProxyImpl, KerberosErrorType} from './kerberos_accounts_browser_proxy.js';
+import {getTemplate} from './kerberos_accounts_subpage.html.js';
 
-const SettingsKerberosAccountsElementBase = DeepLinkingMixin(
+const SettingsKerberosAccountsSubpageElementBase = DeepLinkingMixin(
     RouteObserverMixin(WebUiListenerMixin(I18nMixin(PolymerElement))));
 
-class SettingsKerberosAccountsElement extends
-    SettingsKerberosAccountsElementBase {
+class SettingsKerberosAccountsSubpageElement extends
+    SettingsKerberosAccountsSubpageElementBase {
   static get is() {
-    return 'settings-kerberos-accounts';
+    return 'settings-kerberos-accounts-subpage' as const;
   }
 
   static get template() {
@@ -255,9 +255,11 @@
 
 declare global {
   interface HTMLElementTagNameMap {
-    'settings-kerberos-accounts': SettingsKerberosAccountsElement;
+    [SettingsKerberosAccountsSubpageElement.is]:
+        SettingsKerberosAccountsSubpageElement;
   }
 }
 
 customElements.define(
-    SettingsKerberosAccountsElement.is, SettingsKerberosAccountsElement);
+    SettingsKerberosAccountsSubpageElement.is,
+    SettingsKerberosAccountsSubpageElement);
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.html b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.html
index dcfef19..bd17089 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.html
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.html
@@ -2,7 +2,7 @@
 <os-settings-animated-pages id="pages" section="kerberos"
     focus-config="[[focusConfig_]]">
   <div route-path="default">
-    <cr-link-row id="kerberos-accounts-subpage-trigger"
+    <cr-link-row id="kerberosAccountsSubpageTrigger"
         on-click="onKerberosAccountsClick_"
         label="$i18n{kerberosAccountsSubMenuLabel}"
         role-description="$i18n{subpageArrowRoleDescription}">
@@ -12,7 +12,7 @@
   </div>
   <template is="dom-if" route-path="/kerberos/kerberosAccounts">
     <os-settings-subpage page-title="$i18n{kerberosAccountsPageTitle}">
-      <settings-kerberos-accounts></settings-kerberos-accounts>
+      <settings-kerberos-accounts-subpage></settings-kerberos-accounts-subpage>
     </os-settings-subpage>
   </template>
 </os-settings-animated-pages>
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.ts b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.ts
index a1f9a1d..7f09ae6 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.ts
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.ts
@@ -14,7 +14,6 @@
 import '../os_settings_page/os_settings_animated_pages.js';
 import '../os_settings_page/os_settings_subpage.js';
 import '../../settings_shared.css.js';
-import './kerberos_accounts.js';
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
@@ -47,7 +46,7 @@
           if (routes.KERBEROS_ACCOUNTS_V2) {
             map.set(
                 routes.KERBEROS_ACCOUNTS_V2.path,
-                '#kerberos-accounts-subpage-trigger');
+                '#kerberosAccountsSubpageTrigger');
           }
           return map;
         },
diff --git a/chrome/browser/resources/settings/chromeos/lazy_load.ts b/chrome/browser/resources/settings/chromeos/lazy_load.ts
index 570e5a1..8fc45e9 100644
--- a/chrome/browser/resources/settings/chromeos/lazy_load.ts
+++ b/chrome/browser/resources/settings/chromeos/lazy_load.ts
@@ -25,6 +25,7 @@
 import './internet_page/internet_known_networks_subpage.js';
 import './internet_page/internet_subpage.js';
 import './internet_page/passpoint_subpage.js';
+import './kerberos_page/kerberos_accounts_subpage.js';
 import './os_a11y_page/manage_a11y_subpage.js';
 import './os_a11y_page/display_and_magnification_subpage.js';
 import './os_a11y_page/keyboard_and_text_input_page.js';
@@ -115,6 +116,7 @@
 export {SettingsGuestOsSharedUsbDevicesElement} from './guest_os/guest_os_shared_usb_devices.js';
 export {SettingsPasspointSubpageElement} from './internet_page/passpoint_subpage.js';
 export {TetherConnectionDialogElement} from './internet_page/tether_connection_dialog.js';
+export {KerberosAccount, KerberosAccountsBrowserProxy, KerberosAccountsBrowserProxyImpl, KerberosConfigErrorCode, KerberosErrorType, ValidateKerberosConfigResult} from './kerberos_page/kerberos_accounts_browser_proxy.js';
 export {KeyboardShortcutBanner} from './keyboard_shortcut_banner/keyboard_shortcut_banner.js';
 export {SettingsMultideviceCombinedSetupItemElement} from './multidevice_page/multidevice_combined_setup_item.js';
 export {SettingsMultideviceFeatureItemElement} from './multidevice_page/multidevice_feature_item.js';
@@ -134,7 +136,7 @@
 export {SettingsManageA11ySubpageElement} from './os_a11y_page/manage_a11y_subpage.js';
 export {SettingsSwitchAccessActionAssignmentDialogElement} from './os_a11y_page/switch_access_action_assignment_dialog.js';
 export {SwitchAccessCommand} from './os_a11y_page/switch_access_constants.js';
-export {SettingsTextToSpeechSubpageElement} from './os_a11y_page/text_to_speech_subpage.js';
+export {PdfOcrUserSelection, SettingsTextToSpeechSubpageElement} from './os_a11y_page/text_to_speech_subpage.js';
 export {SettingsTtsVoiceSubpageElement} from './os_a11y_page/tts_voice_subpage.js';
 export {SettingsGoogleDriveSubpageElement} from './os_files_page/google_drive_subpage.js';
 export {SettingsOfficePageElement} from './os_files_page/office_page.js';
@@ -148,10 +150,11 @@
 export {OsSettingsSmartInputsPageElement} from './os_languages_page/smart_inputs_page.js';
 export {AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './os_people_page/account_manager_browser_proxy.js';
 export {SettingsUsersAddUserDialogElement} from './os_people_page/add_user_dialog.js';
-export {FingerprintBrowserProxyImpl, FingerprintResultType} from './os_people_page/fingerprint_browser_proxy.js';
+export {FingerprintBrowserProxy, FingerprintBrowserProxyImpl, FingerprintInfo, FingerprintResultType} from './os_people_page/fingerprint_browser_proxy.js';
+export {SettingsFingerprintListSubpageElement} from './os_people_page/fingerprint_list_subpage.js';
 export {SettingsLockScreenElement} from './os_people_page/lock_screen_subpage.js';
 export {OsSyncBrowserProxy, OsSyncBrowserProxyImpl, OsSyncPrefs} from './os_people_page/os_sync_browser_proxy.js';
-export {FingerprintSetupStep} from './os_people_page/setup_fingerprint_dialog.js';
+export {FingerprintSetupStep, SettingsSetupFingerprintDialogElement} from './os_people_page/setup_fingerprint_dialog.js';
 export {PrinterType} from './os_printing_page/cups_printer_types.js';
 export {CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl, PrinterSetupResult, PrintServerResult} from './os_printing_page/cups_printers_browser_proxy.js';
 export {CupsPrintersEntryManager} from './os_printing_page/cups_printers_entry_manager.js';
@@ -162,6 +165,6 @@
 export {SettingsPrivacyHubSubpage} from './os_privacy_page/privacy_hub_subpage.js';
 export {SettingsSmartPrivacySubpage} from './os_privacy_page/smart_privacy_subpage.js';
 export {OsResetBrowserProxyImpl} from './os_reset_page/os_reset_browser_proxy.js';
-export {GoogleAssistantBrowserProxyImpl} from './os_search_page/google_assistant_browser_proxy.js';
-export {ConsentStatus, DspHotwordState} from './os_search_page/google_assistant_subpage.js';
+export {GoogleAssistantBrowserProxy, GoogleAssistantBrowserProxyImpl} from './os_search_page/google_assistant_browser_proxy.js';
+export {ConsentStatus, DspHotwordState, SettingsGoogleAssistantSubpageElement} from './os_search_page/google_assistant_subpage.js';
 export {SettingsSearchSubpageElement} from './os_search_page/search_subpage.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/bluetooth_braille_display_ui.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/bluetooth_braille_display_ui.ts
index 03d64e4..e897b4e 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/bluetooth_braille_display_ui.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/bluetooth_braille_display_ui.ts
@@ -29,6 +29,11 @@
 const CONNECTED_METRIC_NAME =
     'Accessibility.ChromeVox.BluetoothBrailleDisplayConnectedButtonClick';
 const PINCODE_TIMEOUT_MS = 60000;
+// TODO(b/281743542): Update string for empty braille display picker.
+const BLANK_BRAILLE_DISPLAY_MENU_ITEM = {
+  value: '',
+  name: '',
+};
 
 /**
  * A widget used for interacting with bluetooth braille displays.
@@ -71,7 +76,7 @@
        */
       brailleDisplayMenuOptions_: {
         type: Array,
-        value: [],
+        value: [BLANK_BRAILLE_DISPLAY_MENU_ITEM],
       },
     };
   }
@@ -110,10 +115,21 @@
   }
 
   onDisplayListChanged(displays: chrome.bluetooth.Device[]) {
-    this.brailleDisplayMenuOptions_ = displays.map(display => ({
-                                                     value: display.address,
-                                                     name: display.name!,
-                                                   }));
+    // If there are no displays, just include a blank menu item.
+    if (displays.length === 0) {
+      this.brailleDisplayMenuOptions_ = [BLANK_BRAILLE_DISPLAY_MENU_ITEM];
+    } else {
+      this.brailleDisplayMenuOptions_ = displays.map(display => ({
+                                                       value: display.address,
+                                                       name: display.name!,
+                                                     }));
+      // If the blank option was selected, update the display selection.
+      if (this.brailleDisplayAddressPref_.value === '') {
+        this.set(
+            'brailleDisplayAddressPref_.value',
+            this.selectedAndConnectedDisplayAddress_ || displays[0].address);
+      }
+    }
     this.updateControls_();
   }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.html
index 397e88e..87f14faa 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.html
@@ -83,6 +83,7 @@
 <settings-toggle-button id="crosPdfOcrToggle"
     class="hr" hidden$="[[!showPdfOcrToggle_]]"
     pref="{{prefs.settings.a11y.pdf_ocr_always_active}}"
+    on-change="onPdfOcrToggleChange_"
     label="$i18n{pdfOcrTitle}"
     sub-label="[[getPdfOcrToggleSublabel_(pdfOcrStatus_, pdfOcrProgress_)]]">
 </settings-toggle-button>
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.ts
index 5b2f644..430b019 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_subpage.ts
@@ -12,12 +12,14 @@
 import '/shared/settings/controls/settings_toggle_button.js';
 import '../../settings_shared.css.js';
 
+import {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
 import {PrefsMixin} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {cast} from '../assert_extras.js';
 import {DeepLinkingMixin} from '../deep_linking_mixin.js';
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from '../device_page/device_page_browser_proxy.js';
 import {Setting} from '../mojom-webui/setting.mojom-webui.js';
@@ -29,6 +31,22 @@
 import {TextToSpeechSubpageBrowserProxy, TextToSpeechSubpageBrowserProxyImpl} from './text_to_speech_subpage_browser_proxy.js';
 
 /**
+ * These values are persisted to logs. Entries should not be renumbered and
+ * numeric values should never be reused. This enum is tied directly to a UMA
+ * enum, PdfOcrUserSelection, defined in //tools/metrics/histograms/enums.xml
+ * and should always reflect it (do not change one without changing the other).
+ */
+export enum PdfOcrUserSelection {
+  TURN_ON_ONCE_FROM_CONTEXT_MENU = 0,
+  TURN_ON_ALWAYS_FROM_CONTEXT_MENU = 1,
+  TURN_OFF_FROM_CONTEXT_MENU = 2,
+  TURN_ON_ALWAYS_FROM_MORE_ACTIONS = 3,
+  TURN_OFF_FROM_MORE_ACTIONS = 4,
+  TURN_ON_ALWAYS_FROM_SETTINGS = 5,
+  TURN_OFF_FROM_SETTINGS = 6,
+}
+
+/**
  * Numerical values should not be changed because they must stay in sync with
  * screen_ai::ScreenAIInstallState::State defined in screen_ai_install_state.h
  */
@@ -266,6 +284,18 @@
   private onSelectToSpeakClick_(): void {
     Router.getInstance().navigateTo(routes.A11Y_SELECT_TO_SPEAK);
   }
+
+  private onPdfOcrToggleChange_(event: Event): void {
+    const pdfOcrToggle = cast(event.target, SettingsToggleButtonElement);
+    // Need to divide Object.keys().length by 2 to get the enum size due to
+    // enum reverse mapping in typescript.
+    const enumSize = Object.keys(PdfOcrUserSelection).length / 2;
+    const enumValue = pdfOcrToggle.checked ?
+        PdfOcrUserSelection.TURN_ON_ALWAYS_FROM_SETTINGS :
+        PdfOcrUserSelection.TURN_OFF_FROM_SETTINGS;
+    chrome.metricsPrivate.recordEnumerationValue(
+        'Accessibility.PdfOcr.UserSelection', enumValue, enumSize);
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
index 4387ada..48cbc292 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
@@ -34,6 +34,7 @@
 import {sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {isCrostiniSupported} from '../common/load_time_booleans.js';
 import {DeepLinkingMixin} from '../deep_linking_mixin.js';
 import {MainPageMixin} from '../main_page_mixin.js';
 import {recordSettingChange} from '../metrics_recorder.js';
@@ -162,8 +163,6 @@
         value: 0,
       },
 
-      showCrostini: Boolean,
-
       showCrostiniLicense_: {
         type: Boolean,
         value: false,
@@ -267,7 +266,6 @@
   private eolMessageWithMonthAndYear_: string;
   private hasInternetConnection_: boolean;
   private firmwareUpdateCount_: number;
-  private showCrostini: boolean;
   private showCrostiniLicense_: boolean;
   private showUpdateStatus_: boolean;
   private showButtonContainer_: boolean;
@@ -660,7 +658,7 @@
    * @param enabled True if Crostini is enabled.
    */
   private handleCrostiniEnabledChanged_(enabled: boolean) {
-    this.showCrostiniLicense_ = enabled && this.showCrostini;
+    this.showCrostiniLicense_ = enabled && isCrostiniSupported();
   }
 
   private shouldShowSafetyInfo_(): boolean {
diff --git a/chrome/browser/resources/settings/chromeos/os_page_availability.ts b/chrome/browser/resources/settings/chromeos/os_page_availability.ts
index 34346046..dded05ba 100644
--- a/chrome/browser/resources/settings/chromeos/os_page_availability.ts
+++ b/chrome/browser/resources/settings/chromeos/os_page_availability.ts
@@ -15,7 +15,7 @@
  * visible page.
  */
 
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {isGuest, isKerberosEnabled, isPowerwashAllowed} from './common/load_time_booleans.js';
 
 export interface OsPageAvailability {
   a11y: boolean;
@@ -29,10 +29,7 @@
   kerberos: boolean;
   languages: boolean;
   multidevice: boolean;
-  people: boolean|{
-    googleAccounts: boolean,
-    lockScreen: boolean,
-  };
+  people: boolean;
   personalization: boolean;
   printing: boolean;
   privacy: boolean;
@@ -47,15 +44,7 @@
  * overriding load time data within tests.
  */
 export function createPageAvailability(): OsPageAvailability {
-  const isGuestMode = loadTimeData.getBoolean('isGuest');
-  const isAccountManagerEnabled =
-      loadTimeData.valueExists('isAccountManagerEnabled') &&
-      loadTimeData.getBoolean('isAccountManagerEnabled');
-  const isKerberosEnabled = loadTimeData.valueExists('isKerberosEnabled') &&
-      loadTimeData.getBoolean('isKerberosEnabled');
-  const isPowerwashAllowed = loadTimeData.getBoolean('allowPowerwash');
-
-  if (isGuestMode) {
+  if (isGuest()) {
     return {
       a11y: true,
       apps: true,
@@ -65,14 +54,14 @@
       device: true,
       files: false,
       internet: true,
-      kerberos: isKerberosEnabled,
+      kerberos: isKerberosEnabled(),
       languages: true,
       multidevice: false,
       people: false,
       personalization: false,
       printing: true,
       privacy: true,
-      reset: isPowerwashAllowed,
+      reset: isPowerwashAllowed(),
       search: true,
     };
   }
@@ -85,17 +74,14 @@
     device: true,
     files: true,
     internet: true,
-    kerberos: isKerberosEnabled,
+    kerberos: isKerberosEnabled(),
     languages: true,
     multidevice: true,
-    people: {
-      googleAccounts: isAccountManagerEnabled,
-      lockScreen: true,
-    },
+    people: true,
     personalization: true,
     printing: true,
     privacy: true,
-    reset: isPowerwashAllowed,
+    reset: isPowerwashAllowed(),
     search: true,
   };
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list_subpage.ts b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list_subpage.ts
index 5f7f61c0..c757da5 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list_subpage.ts
@@ -34,7 +34,7 @@
 const SettingsFingerprintListSubpageElementBase = RouteObserverMixin(
     WebUiListenerMixin(I18nMixin(DeepLinkingMixin(PolymerElement))));
 
-class SettingsFingerprintListSubpageElement extends
+export class SettingsFingerprintListSubpageElement extends
     SettingsFingerprintListSubpageElementBase {
   static get is() {
     return 'settings-fingerprint-list-subpage' as const;
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts
index 96c5161..03b38111 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts
@@ -26,6 +26,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {afterNextRender, flush, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {isAccountManagerEnabled} from '../common/load_time_booleans.js';
 import {DeepLinkingMixin} from '../deep_linking_mixin.js';
 import {LockStateMixin} from '../lock_state_mixin.js';
 import {Setting} from '../mojom-webui/setting.mojom-webui.js';
@@ -92,7 +93,7 @@
       isAccountManagerEnabled_: {
         type: Boolean,
         value() {
-          return loadTimeData.getBoolean('isAccountManagerEnabled');
+          return isAccountManagerEnabled();
         },
         readOnly: true,
       },
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.ts b/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.ts
index cbdc808..0eac589 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.ts
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.ts
@@ -53,14 +53,14 @@
 const SettingsSetupFingerprintDialogElementBase =
     I18nMixin(WebUiListenerMixin(PolymerElement));
 
-interface SettingsSetupFingerprintDialogElement {
+export interface SettingsSetupFingerprintDialogElement {
   $: {
     dialog: CrDialogElement,
     arc: CrFingerprintProgressArcElement,
   };
 }
 
-class SettingsSetupFingerprintDialogElement extends
+export class SettingsSetupFingerprintDialogElement extends
     SettingsSetupFingerprintDialogElementBase {
   static get is() {
     return 'settings-setup-fingerprint-dialog' as const;
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.html b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.html
index a030914..a21030a 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.html
@@ -73,6 +73,18 @@
   </div>
 </div>
 
+<template is="dom-if" if="[[showSpeakOnMuteDetectionPage_]]" restamp>
+  <settings-toggle-button
+      class="settings-box"
+      pref="{{prefs.ash.user.speak_on_mute_detection_enabled}}"
+      id="speakonmuteDetectionToggle"
+      label="$i18n{speakOnMuteDetectionToggleTitle}"
+      sub-label="$i18n{speakOnMuteDetectionToggleSubtext}"
+      deep-link-focus-id$="[[Setting.kSpeakOnMuteDetectionOnOff]]"
+      learn-more-url="$i18n{speakOnMuteDetectionLearnMoreURL}">
+  </settings-toggle-button>
+</template>
+
 <!-- Location toggle is not to be shown in dogfooded version of Privacy Hub -->
 <template is="dom-if" if="[[showPrivacyHubMVPPage_]]" restamp>
   <settings-toggle-button
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.ts b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.ts
index fa842b8..5b9d03f 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_subpage.ts
@@ -105,6 +105,17 @@
       },
 
       /**
+       * Whether the part of speak-on-mute detection should be displayed.
+       */
+      showSpeakOnMuteDetectionPage_: {
+        type: Boolean,
+        readOnly: true,
+        value: () => {
+          return loadTimeData.getBoolean('showSpeakOnMuteDetectionPage');
+        },
+      },
+
+      /**
        * Used by DeepLinkingMixin to focus this page's deep links.
        */
       supportedSettingIds: {
@@ -112,6 +123,7 @@
         value: () => new Set<Setting>([
           Setting.kCameraOnOff,
           Setting.kMicrophoneOnOff,
+          Setting.kSpeakOnMuteDetectionOnOff,
           Setting.kGeolocationOnOff,
           Setting.kUsageStatsAndCrashReports,
         ]),
@@ -127,6 +139,7 @@
   private microphoneHardwareToggleActive_: boolean;
   private shouldDisableMicrophoneToggle_: boolean;
   private showPrivacyHubMVPPage_: boolean;
+  private showSpeakOnMuteDetectionPage_: boolean;
 
   constructor() {
     super();
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/google_assistant_subpage.ts b/chrome/browser/resources/settings/chromeos/os_search_page/google_assistant_subpage.ts
index 87b3d790..5915ab2 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/google_assistant_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/google_assistant_subpage.ts
@@ -67,7 +67,7 @@
     DeepLinkingMixin(RouteObserverMixin(
         PrefsMixin(WebUiListenerMixin(I18nMixin(PolymerElement)))));
 
-class SettingsGoogleAssistantSubpageElement extends
+export class SettingsGoogleAssistantSubpageElement extends
     SettingsGoogleAssistantSubpageElementBase {
   static get is() {
     return 'settings-google-assistant-subpage';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index fab5225..252cc1b 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -89,7 +89,7 @@
   "chromeos/internet_page/passpoint_subpage.ts",
   "chromeos/internet_page/settings_traffic_counters.ts",
   "chromeos/internet_page/tether_connection_dialog.ts",
-  "chromeos/kerberos_page/kerberos_accounts.ts",
+  "chromeos/kerberos_page/kerberos_accounts_subpage.ts",
   "chromeos/kerberos_page/kerberos_add_account_dialog.ts",
   "chromeos/kerberos_page/kerberos_page.ts",
   "chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.ts",
@@ -302,6 +302,7 @@
 non_web_component_files = [
   "chromeos/assert_extras.ts",
   "chromeos/common/global_scroll_target_mixin.ts",
+  "chromeos/common/load_time_booleans.ts",
   "chromeos/common/setting_id_param_util.ts",
   "chromeos/common/types.ts",
   "chromeos/os_files_page/google_drive_browser_proxy.ts",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.ts b/chrome/browser/resources/settings/chromeos/os_settings.ts
index 212315fae..1a5dba1 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings.ts
@@ -40,8 +40,6 @@
 import './device_page/storage_external.js';
 import './device_page/storage_external_entry.js';
 import './device_page/stylus.js';
-import './kerberos_page/kerberos_accounts.js';
-import './kerberos_page/kerberos_page.js';
 import './multidevice_page/multidevice_page.js';
 import './nearby_share_page/nearby_share_high_visibility_page.js';
 import './nearby_share_page/nearby_share_receive_dialog.js';
@@ -96,6 +94,7 @@
 export * as nearbyShareMojom from '/shared/nearby_share.mojom-webui.js';
 export {getNearbyShareSettings, observeNearbyShareSettings, setNearbyShareSettingsForTesting} from '/shared/nearby_share_settings.js';
 export {NearbySettings, NearbyShareSettingsMixin} from '/shared/nearby_share_settings_mixin.js';
+export {ControlledButtonElement} from '/shared/settings/controls/controlled_button.js';
 export {SettingsDropdownMenuElement} from '/shared/settings/controls/settings_dropdown_menu.js';
 export {SettingsSliderElement} from '/shared/settings/controls/settings_slider.js';
 export {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
@@ -130,7 +129,6 @@
 export {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page/internet_page_browser_proxy.js';
 export {NetworkSummaryElement} from './internet_page/network_summary.js';
 export {NetworkSummaryItemElement} from './internet_page/network_summary_item.js';
-export {KerberosAccount, KerberosAccountsBrowserProxy, KerberosAccountsBrowserProxyImpl, KerberosConfigErrorCode, KerberosErrorType, ValidateKerberosConfigResult} from './kerberos_page/kerberos_accounts_browser_proxy.js';
 export {SettingsKerberosPageElement} from './kerberos_page/kerberos_page.js';
 export {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from './metrics_recorder.js';
 export * as appNotificationHandlerMojom from './mojom-webui/app_notification_handler.mojom-webui.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html
index a733f8b..01056a23 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html
@@ -43,14 +43,12 @@
       page-availability="[[pageAvailability]]"
       show-android-apps="[[showAndroidApps]]"
       show-arcvm-manage-usb="[[showArcvmManageUsb]]";
-      show-crostini="[[showCrostini]]"
       show-plugin-vm="[[showPluginVm]]"
       have-play-store-app="[[havePlayStoreApp]]"
       on-showing-section="onShowingSection_"
       on-subpage-expand="onShowingSubpage_"
       on-showing-main-page="onShowingMainPage_"
-      advanced-toggle-expanded="{{advancedToggleExpanded}}"
-      show-kerberos-section="[[showKerberosSection]]">
+      advanced-toggle-expanded="{{advancedToggleExpanded}}">
   </os-settings-page>
 </template>
 <template is="dom-if" if="[[showPages_.about]]">
@@ -59,8 +57,7 @@
       on-subpage-expand="onShowingSubpage_"
       on-showing-section="onShowingSection_"
       on-showing-main-page="onShowingMainPage_"
-      prefs="{{prefs}}"
-      show-crostini="[[showCrostini]]">
+      prefs="{{prefs}}">
   </os-settings-about-page>
 </template>
 <div id="overscroll" style="padding-bottom: [[overscroll_]]px"></div>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts
index 238cb82..de12b22 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts
@@ -98,10 +98,6 @@
 
       showArcvmManageUsb: Boolean,
 
-      showCrostini: Boolean,
-
-      showKerberosSection: Boolean,
-
       havePlayStoreApp: Boolean,
     };
   }
@@ -112,8 +108,6 @@
   pageAvailability: OsPageAvailability;
   showAndroidApps: boolean;
   showArcvmManageUsb: boolean;
-  showCrostini: boolean;
-  showKerberosSection: boolean;
   havePlayStoreApp: boolean;
   private overscroll_: number;
   private showPages_: MainPageVisibility;
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
index a8e9f78f..20f08ba 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
@@ -155,7 +155,8 @@
         </os-settings-people-page>
       </os-settings-section>
     </template>
-    <template is="dom-if" if="[[showKerberosSection]]" restamp>
+    <template is="dom-if"
+        if="[[shouldStampPage_(pageAvailability.kerberos)]]" restamp>
       <os-settings-section page-title="$i18n{kerberosPageTitle}"
           section="kerberos">
         <settings-kerberos-page></settings-kerberos-page>
@@ -163,8 +164,7 @@
     </template>
     <os-settings-section page-title="$i18n{devicePageTitle}"
         section="device">
-      <settings-device-page prefs="{{prefs}}"
-          show-crostini="[[showCrostini]]">
+      <settings-device-page prefs="{{prefs}}">
       </settings-device-page>
     </os-settings-section>
     <template is="dom-if" if="[[!isGuestMode_]]" restamp>
@@ -245,9 +245,7 @@
       </os-settings-section>
       <os-settings-section page-title="$i18n{crostiniPageTitle}"
           section="crostini">
-        <settings-crostini-page prefs="{{prefs}}"
-            allow-crostini="[[allowCrostini_]]"
-            show-crostini="[[showCrostini]]">
+        <settings-crostini-page prefs="{{prefs}}">
         </settings-crostini-page>
       </os-settings-section>
       <template is="dom-if"
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
index 5b40055..6417023 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
@@ -75,14 +75,8 @@
 
       showArcvmManageUsb: Boolean,
 
-      showCrostini: Boolean,
-
       showPluginVm: Boolean,
 
-      showKerberosSection: Boolean,
-
-      allowCrostini_: Boolean,
-
       havePlayStoreApp: Boolean,
 
       androidAppsInfo: Object,
@@ -156,8 +150,6 @@
   androidAppsInfo?: AndroidAppsInfo;
   pageAvailability: OsPageAvailability;
   advancedToggleExpanded: boolean;
-  showKerberosSection: boolean;
-  private allowCrostini_: boolean;
   private hasExpandedSection_: boolean;
   private showSecondaryUserBanner_: boolean;
   private showUpdateRequiredEolBanner_: boolean;
@@ -186,9 +178,6 @@
 
     this.currentRoute_ = Router.getInstance().currentRoute;
 
-    this.allowCrostini_ = loadTimeData.valueExists('allowCrostini') &&
-        loadTimeData.getBoolean('allowCrostini');
-
     this.addWebUiListener(
         'android-apps-info-update', this.androidAppsInfoUpdate_.bind(this));
     AndroidAppsBrowserProxyImpl.getInstance().requestAndroidAppsInfo();
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_routes.ts b/chrome/browser/resources/settings/chromeos/os_settings_routes.ts
index 4dc7ce2..9b8a65be 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_routes.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_routes.ts
@@ -5,6 +5,7 @@
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
+import {isCrostiniSupported, isGuest, isKerberosEnabled, isPowerwashAllowed} from './common/load_time_booleans.js';
 import * as routesMojom from './mojom-webui/routes.mojom-webui.js';
 
 /** Class for navigable routes. */
@@ -272,7 +273,7 @@
   }
 
   // MultiDevice section.
-  if (!loadTimeData.getBoolean('isGuest')) {
+  if (!isGuest()) {
     r.MULTIDEVICE = createSection(
         r.BASIC, routesMojom.MULTI_DEVICE_SECTION_PATH, Section.kMultiDevice);
     r.MULTIDEVICE_FEATURES = createSubpage(
@@ -286,7 +287,7 @@
   }
 
   // People section.
-  if (!loadTimeData.getBoolean('isGuest')) {
+  if (!isGuest()) {
     r.OS_PEOPLE = createSection(
         r.BASIC, routesMojom.PEOPLE_SECTION_PATH, Section.kPeople);
     r.ACCOUNT_MANAGER = createSubpage(
@@ -298,8 +299,7 @@
   }
 
   // Kerberos section.
-  if (loadTimeData.valueExists('isKerberosEnabled') &&
-      loadTimeData.getBoolean('isKerberosEnabled')) {
+  if (isKerberosEnabled()) {
     r.KERBEROS = createSection(
         r.BASIC, routesMojom.KERBEROS_SECTION_PATH, Section.kKerberos);
     r.KERBEROS_ACCOUNTS_V2 = createSubpage(
@@ -349,7 +349,7 @@
       createSubpage(r.DEVICE, routesMojom.POWER_SUBPAGE_PATH, Subpage.kPower);
 
   // Personalization section.
-  if (!loadTimeData.getBoolean('isGuest')) {
+  if (!isGuest()) {
     r.PERSONALIZATION = createSection(
         r.BASIC, routesMojom.PERSONALIZATION_SECTION_PATH,
         Section.kPersonalization);
@@ -445,8 +445,7 @@
   // Crostini section.
   r.CROSTINI = createSection(
       r.ADVANCED, routesMojom.CROSTINI_SECTION_PATH, Section.kCrostini);
-  if (loadTimeData.valueExists('showCrostini') &&
-      loadTimeData.getBoolean('showCrostini')) {
+  if (isCrostiniSupported()) {
     r.CROSTINI_DETAILS = createSubpage(
         r.CROSTINI, routesMojom.CROSTINI_DETAILS_SUBPAGE_PATH,
         Subpage.kCrostiniDetails);
@@ -542,7 +541,7 @@
 
 
   // Files section.
-  if (!loadTimeData.getBoolean('isGuest')) {
+  if (!isGuest()) {
     r.FILES = createSection(
         r.ADVANCED, routesMojom.FILES_SECTION_PATH, Section.kFiles);
     r.SMB_SHARES = createSubpage(
@@ -564,8 +563,7 @@
       Subpage.kPrintingDetails);
 
   // Reset section.
-  if (loadTimeData.valueExists('allowPowerwash') &&
-      loadTimeData.getBoolean('allowPowerwash')) {
+  if (isPowerwashAllowed()) {
     r.OS_RESET = createSection(
         r.ADVANCED, routesMojom.RESET_SECTION_PATH, Section.kReset);
   }
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html
index e94d9ba1..fe2d123 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html
@@ -143,11 +143,9 @@
       page-availability="[[pageAvailability_]]"
       show-android-apps="[[showAndroidApps_]]"
       show-arcvm-manage-usb="[[showArcvmManageUsb_]]"
-      show-crostini="[[showCrostini_]]"
       show-plugin-vm="[[showPluginVm_]]"
       have-play-store-app="[[havePlayStoreApp_]]"
-      advanced-toggle-expanded="{{advancedOpenedInMain_}}"
-      show-kerberos-section="[[showKerberosSection_]]">
+      advanced-toggle-expanded="{{advancedOpenedInMain_}}">
   </os-settings-main>
   <!-- An additional child of the flex #container to take up space,
         aligned with the right-hand child of the flex toolbar. -->
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
index 2d9841d..5a5807f 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
@@ -146,16 +146,12 @@
 
       showArcvmManageUsb_: Boolean,
 
-      showCrostini_: Boolean,
-
       showToolbar_: Boolean,
 
       showNavMenu_: Boolean,
 
       showPluginVm_: Boolean,
 
-      showKerberosSection_: Boolean,
-
       /**
        * The threshold at which the toolbar will change from normal to narrow
        * mode, in px.
@@ -176,11 +172,9 @@
   private havePlayStoreApp_: boolean;
   private showAndroidApps_: boolean;
   private showArcvmManageUsb_: boolean;
-  private showCrostini_: boolean;
   private showToolbar_: boolean;
   private showNavMenu_: boolean;
   private showPluginVm_: boolean;
-  private showKerberosSection_: boolean;
   private narrowThreshold_: number;
   private activeRoute_: Route|null;
   private scrollEndDebouncer_: Debouncer|null;
@@ -235,14 +229,10 @@
     this.havePlayStoreApp_ = loadTimeData.getBoolean('havePlayStoreApp');
     this.showAndroidApps_ = loadTimeData.getBoolean('androidAppsVisible');
     this.showArcvmManageUsb_ = loadTimeData.getBoolean('showArcvmManageUsb');
-    this.showCrostini_ = loadTimeData.getBoolean('showCrostini');
     this.showPluginVm_ = loadTimeData.getBoolean('showPluginVm');
     this.showNavMenu_ = !loadTimeData.getBoolean('isKioskModeActive');
     this.showToolbar_ = !loadTimeData.getBoolean('isKioskModeActive');
 
-    this.showKerberosSection_ = loadTimeData.valueExists('isKerberosEnabled') &&
-        loadTimeData.getBoolean('isKerberosEnabled');
-
     this.addEventListener('show-container', () => {
       this.$.container.style.visibility = 'visible';
     });
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.html b/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.html
index da4cf74..8f46cad 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.html
@@ -11,7 +11,7 @@
   <img id="banner" alt=""
       src="chrome://settings/images/ad_privacy_banner.svg">
 </picture>
-<template is="dom-if" if="[[!isPrivacySandboxRestrictedNoticeEnabled_]]">
+<template is="dom-if" if="[[!isPrivacySandboxRestricted_]]">
   <cr-link-row
       id="privacySandboxTopicsLinkRow"
       start-icon="settings20:interests"
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.ts b/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.ts
index eee0b2f..ac8a562 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.ts
+++ b/chrome/browser/resources/settings/privacy_page/privacy_sandbox/privacy_sandbox_page.ts
@@ -47,15 +47,14 @@
         notify: true,
       },
 
-      isPrivacySandboxRestrictedNoticeEnabled_: {
+      isPrivacySandboxRestricted_: {
         type: Boolean,
-        value: () =>
-            loadTimeData.getBoolean('isPrivacySandboxRestrictedNoticeEnabled'),
+        value: () => loadTimeData.getBoolean('isPrivacySandboxRestricted'),
       },
     };
   }
 
-  private isPrivacySandboxRestrictedNoticeEnabled_: boolean;
+  private isPrivacySandboxRestricted_: boolean;
   private metricsBrowserProxy_: MetricsBrowserProxy =
       MetricsBrowserProxyImpl.getInstance();
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.html b/chrome/browser/resources/side_panel/customize_chrome/app.html
index 84f0f1e..e8adc519 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.html
@@ -8,7 +8,7 @@
     margin: 8px 16px;
   }
 
-  .card-separator {
+  .sp-cards-separator {
     background-color: var(--google-grey-300);
     border: none;
     height: 2px;
@@ -17,14 +17,13 @@
   }
 
   @media (prefers-color-scheme: dark) {
-    .card-separator {
+    .sp-cards-separator {
       background-color: var(--google-grey-700);
     }
   }
 
-  :host-context([chrome-refresh-2023]) .card-separator {
+  :host-context([chrome-refresh-2023]) .sp-cards-separator {
     background: transparent;
-    height: 8px;
   }
 
   :host-context([chrome-refresh-2023]) .sp-card {
@@ -56,7 +55,7 @@
         id="appearanceElement">
       </customize-chrome-appearance>
     </div>
-    <hr class="card-separator">
+    <hr class="sp-cards-separator">
     <div id="shortcuts" class="section sp-card">
       <sp-heading hide-back-button>
         <h2 slot="heading">$i18n{shortcutsHeader}</h2>
@@ -64,7 +63,7 @@
       <customize-chrome-shortcuts></customize-chrome-shortcuts>
     </div>
     <template is="dom-if" if="[[modulesEnabled_]]">
-      <hr class="card-separator">
+      <hr class="sp-cards-separator">
       <div id="modules" class="section sp-card">
         <sp-heading hide-back-button>
           <h2 slot="heading">$i18n{cardsHeader}</h2>
diff --git a/chrome/browser/resources/side_panel/shared/sp_shared_style.css b/chrome/browser/resources/side_panel/shared/sp_shared_style.css
index 6d920e4..83461e40 100644
--- a/chrome/browser/resources/side_panel/shared/sp_shared_style.css
+++ b/chrome/browser/resources/side_panel/shared/sp_shared_style.css
@@ -116,3 +116,11 @@
 :host-context([chrome-refresh-2023]) .sp-hr {
   background: var(--color-side-panel-divider);
 }
+
+:host-context([chrome-refresh-2023]) .sp-cards-separator {
+  border: 0;
+  flex-shrink: 0;
+  height: 8px;
+  margin: 0;
+  width: 100%;
+}
diff --git a/chrome/browser/resources/signin/profile_picker/BUILD.gn b/chrome/browser/resources/signin/profile_picker/BUILD.gn
index 1604c2b..2a142714 100644
--- a/chrome/browser/resources/signin/profile_picker/BUILD.gn
+++ b/chrome/browser/resources/signin/profile_picker/BUILD.gn
@@ -11,13 +11,7 @@
 build_webui("build") {
   grd_prefix = "profile_picker"
 
-  static_files = [
-    "profile_picker.html",
-    "images/left_banner_image.svg",
-    "images/right_banner_image.svg",
-    "images/dark_mode_left_banner_image.svg",
-    "images/dark_mode_right_banner_image.svg",
-  ]
+  static_files = [ "profile_picker.html" ]
 
   web_component_files = [
     "profile_card_menu.ts",
diff --git a/chrome/browser/resources/signin/profile_picker/images/dark_mode_left_banner_image.svg b/chrome/browser/resources/signin/profile_picker/images/dark_mode_left_banner_image.svg
deleted file mode 100644
index 788d6c3..0000000
--- a/chrome/browser/resources/signin/profile_picker/images/dark_mode_left_banner_image.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="169" height="400" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m75.7 64.2-16.3-5.1c-1.7-.5-3.4.4-4 2.1l-5.1 16.3c-.5 1.7.4 3.4 2.1 4l16.3 5.1c1.7.5 3.4-.4 4-2.1l5.1-16.3c.5-1.7-.5-3.4-2.1-4z" fill="#8AB4F8"/><path d="M103.8 242.1a6.9 6.9 0 1 0 0-13.8 6.9 6.9 0 0 0 0 13.8z" fill="#FDD663"/><path d="m81.3 277.3-13 11.1c-.7.6-1 1.5-.9 2.4l3.1 16.7c.2.9.8 1.6 1.7 1.9l16.1 5.7c.9.3 1.8.1 2.5-.5l13-11.1c.7-.6 1-1.5.9-2.4l-3.1-16.7c-.2-.9-.8-1.6-1.7-1.9l-16.1-5.7c-.9-.3-1.8-.1-2.5.5zm-42.4 74c1.6 2.1 1.2 5.2-1 6.8-2.2 1.6-5.2 1.2-6.8-1-1.6-2.1-1.2-5.2 1-6.8 2.2-1.5 5.2-1.1 6.8 1zm-10.4-5.7c-4.8 3.6-5.7 10.3-2.2 15.1 3.6 4.8 10.3 5.7 15.1 2.2 4.8-3.5 5.7-10.3 2.2-15.1-3.5-4.8-10.3-5.8-15.1-2.2z" fill="#616161"/><path d="m66.4 164-32.2 18.6c-1.2.7-1.6 2.3-.9 3.5l7.7 13.4c.7 1.2 2.3 1.6 3.5.9l32.2-18.6c1.2-.7 1.6-2.3.9-3.5l-7.7-13.4c-.7-1.2-2.3-1.7-3.5-.9z" stroke="#81C995" stroke-width="2" stroke-miterlimit="10"/><path d="M162.1 81.9c2.8-4.9 1-11.2-3.9-14-4.9-2.8-11.2-1-14 3.9l-13.3 23.5c-2.8 4.9-1 11.2 3.9 14 4.9 2.8 11.2 1 14-3.9l13.3-23.5z" fill="#616161"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/signin/profile_picker/images/dark_mode_right_banner_image.svg b/chrome/browser/resources/signin/profile_picker/images/dark_mode_right_banner_image.svg
deleted file mode 100644
index 2152057..0000000
--- a/chrome/browser/resources/signin/profile_picker/images/dark_mode_right_banner_image.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="169" height="400" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M48.5 182c-2.7 0-5.4-1-7.5-3l-19.8-18.4c-2.2-2-3.4-4.8-3.5-7.7-.1-3 .9-5.8 3-8 4.2-4.5 11.2-4.7 15.7-.6l19.8 18.4c2.2 2 3.4 4.8 3.5 7.7.1 3-.9 5.8-3 8-2.3 2.4-5.2 3.6-8.2 3.6zm-19.8-38.9c-2.5 0-5.1 1-6.9 3-1.7 1.9-2.6 4.3-2.5 6.8.1 2.5 1.2 4.9 3 6.6l19.8 18.4c3.8 3.6 9.8 3.3 13.4-.5 1.7-1.9 2.6-4.3 2.5-6.8-.1-2.5-1.2-4.9-3-6.6l-19.8-18.4c-1.8-1.7-4.1-2.5-6.5-2.5z" fill="#616161"/><path d="M75.7 257.4c5.689 0 10.3-4.612 10.3-10.3 0-5.689-4.611-10.3-10.3-10.3-5.688 0-10.3 4.611-10.3 10.3 0 5.688 4.612 10.3 10.3 10.3z" fill="#8AB4F8"/><path d="m117.201 184-8.7 7.2c-.7.6-1 1.5-.9 2.4l1.9 11.1c.2.9.8 1.7 1.6 2l10.6 3.9c.9.3 1.8.2 2.5-.4l8.7-7.2c.7-.6 1-1.5.9-2.4l-1.9-11.1c-.2-.9-.8-1.7-1.6-2l-10.6-3.9c-.8-.3-1.8-.1-2.5.4z" fill="#FDD663"/><path d="M111.599 376.2a6.9 6.9 0 1 0 0-13.8 6.9 6.9 0 0 0 0 13.8zm29.501-93.6-33.9 9.1c-1.9.5-2.5 2.9-1.1 4.3l24.8 24.8c1.4 1.4 3.8.8 4.3-1.1l9.1-33.9c.5-2-1.3-3.7-3.2-3.2z" fill="#616161"/><path d="M91.8 65.1c-3.4 1.8-5.5 4.8-6.2 8.2-.5 2.3-1.8 4.3-3.7 5.6l-.9.6c-1.9 1.3-4.3 1.6-6.6 1.2-3.4-.7-7 .1-10 2.5-4.4 3.5-5.8 9.9-3.2 14.9 3.3 6.601 11.5 8.601 17.5 4.701 2.8-1.8 4.6-4.6 5.2-7.6.5-2.2 1.8-4.2 3.7-5.5l.9-.6c1.9-1.3 4.2-1.7 6.5-1.2 3 .6 6.301.1 9.101-1.7 6-3.9 7.4-12.3 2.7-17.9-3.7-4.6-10-5.8-15-3.2z" stroke="#81C995" stroke-width="2" stroke-miterlimit="10"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/signin/profile_picker/images/left_banner_image.svg b/chrome/browser/resources/signin/profile_picker/images/left_banner_image.svg
deleted file mode 100644
index 8a7a7dc..0000000
--- a/chrome/browser/resources/signin/profile_picker/images/left_banner_image.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="169" height="400" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m75.7 64.2-16.3-5.1c-1.7-.5-3.4.4-4 2.1l-5.1 16.3c-.5 1.7.4 3.4 2.1 4l16.3 5.1c1.7.5 3.4-.4 4-2.1l5.1-16.3c.5-1.7-.5-3.4-2.1-4z" fill="#4285F4"/><path d="M103.8 242.101a6.9 6.9 0 1 0 0-13.8 6.9 6.9 0 0 0 0 13.8z" fill="#FBBC05"/><path d="m81.3 277.3-13 11.1c-.7.6-1 1.5-.9 2.4l3.1 16.7c.2.9.8 1.6 1.7 1.9l16.1 5.7c.9.3 1.8.1 2.5-.5l13-11.1c.7-.6 1-1.5.9-2.4l-3.1-16.7c-.2-.9-.8-1.6-1.7-1.9l-16.1-5.7c-.9-.3-1.8-.1-2.5.5zm-42.4 74.001c1.6 2.1 1.2 5.2-1 6.8-2.2 1.6-5.2 1.2-6.8-1-1.6-2.1-1.2-5.2 1-6.8 2.2-1.5 5.2-1.1 6.8 1zm-10.4-5.7c-4.8 3.6-5.7 10.3-2.2 15.1 3.6 4.8 10.3 5.7 15.1 2.2 4.8-3.5 5.7-10.3 2.2-15.1-3.5-4.8-10.3-5.8-15.1-2.2z" fill="#F1F3F4"/><path d="m66.4 164-32.2 18.6c-1.2.7-1.6 2.3-.9 3.5l7.7 13.4c.7 1.2 2.3 1.6 3.5.9l32.2-18.6c1.2-.7 1.6-2.3.9-3.5l-7.7-13.4c-.7-1.2-2.3-1.7-3.5-.9z" stroke="#34A853" stroke-width="2" stroke-miterlimit="10"/><path d="M162.1 81.9c2.8-4.9 1-11.2-3.9-14-4.9-2.8-11.2-1-14 3.9l-13.3 23.5c-2.8 4.9-1 11.2 3.9 14 4.9 2.8 11.2 1 14-3.9l13.3-23.5z" fill="#F1F3F4"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/signin/profile_picker/images/right_banner_image.svg b/chrome/browser/resources/signin/profile_picker/images/right_banner_image.svg
deleted file mode 100644
index 3d99c51..0000000
--- a/chrome/browser/resources/signin/profile_picker/images/right_banner_image.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="169" height="400" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M48.5 181.999c-2.7 0-5.4-1-7.5-3l-19.8-18.4c-2.2-2-3.4-4.8-3.5-7.7-.1-3 .9-5.8 3-8 4.2-4.5 11.2-4.7 15.7-.6l19.8 18.4c2.2 2 3.4 4.8 3.5 7.7.1 3-.9 5.8-3 8-2.3 2.4-5.2 3.6-8.2 3.6zm-19.8-38.9c-2.5 0-5.1 1-6.9 3-1.7 1.9-2.6 4.3-2.5 6.8.1 2.5 1.2 4.9 3 6.6l19.8 18.4c3.8 3.6 9.8 3.3 13.4-.5 1.7-1.9 2.6-4.3 2.5-6.8-.1-2.5-1.2-4.9-3-6.6l-19.8-18.4c-1.8-1.7-4.1-2.5-6.5-2.5z" fill="#E8EAED"/><path d="M75.7 257.401c5.689 0 10.3-4.612 10.3-10.3 0-5.689-4.611-10.3-10.3-10.3-5.688 0-10.3 4.611-10.3 10.3 0 5.688 4.612 10.3 10.3 10.3z" fill="#4285F4"/><path d="m117.201 184-8.7 7.2c-.7.6-1 1.5-.9 2.4l1.9 11.1c.2.9.8 1.7 1.6 2l10.6 3.9c.9.3 1.8.2 2.5-.4l8.7-7.2c.7-.6 1-1.5.9-2.4l-1.9-11.1c-.2-.9-.8-1.7-1.6-2l-10.6-3.9c-.8-.3-1.8-.1-2.5.4z" fill="#FBBC05"/><path d="M111.599 376.2a6.9 6.9 0 1 0 0-13.8 6.9 6.9 0 0 0 0 13.8zm29.501-93.6-33.9 9.1c-1.9.5-2.5 2.9-1.1 4.3l24.8 24.8c1.4 1.4 3.8.8 4.3-1.1l9.1-33.9c.5-2-1.3-3.7-3.2-3.2z" fill="#F1F3F4"/><path d="M91.8 65.1c-3.4 1.8-5.5 4.8-6.2 8.2-.5 2.3-1.8 4.3-3.7 5.6l-.9.6c-1.9 1.3-4.3 1.6-6.6 1.2-3.4-.7-7 .1-10 2.5-4.4 3.5-5.8 9.9-3.2 14.9 3.3 6.601 11.5 8.601 17.5 4.701 2.8-1.8 4.6-4.6 5.2-7.6.5-2.2 1.8-4.2 3.7-5.5l.9-.6c1.9-1.3 4.2-1.7 6.5-1.2 3 .6 6.301.1 9.101-1.7 6-3.9 7.4-12.3 2.7-17.9-3.7-4.6-10-5.8-15-3.2z" stroke="#34A853" stroke-width="2" stroke-miterlimit="10"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.html b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.html
index 5f31573..57a20d9 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.html
+++ b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.html
@@ -51,15 +51,10 @@
   }
 
   cr-button {
-    font-size: 1em;
-    font-weight: normal;
-    min-width: 210px;
-  }
-
-  /* Tangible sync style details are found in go/tangible-sync-ui */
-  .tangible-sync-style cr-button {
     --cr-button-height: 36px;
     font-size: 0.75rem;
+    font-weight: normal;
+    min-width: 210px;
   }
 
   #notNowButton {
@@ -72,14 +67,14 @@
     text-align: center;
   }
 
-  .tangible-sync-style .title {
+  .title {
     font-size: 2.25rem;
     font-weight: 500;
     line-height: 43px;
     margin-bottom: 16px;
   }
 
-  .tangible-sync-style .subtitle {
+  .subtitle {
     font-size: 1.25rem;
     line-height: 24px;
   }
@@ -95,21 +90,22 @@
     }
   }
 
-@media screen and ((max-width: 780px) or (max-height: 600px)) {
-  .tangible-sync-style .title {
-    font-size: 1.5rem;
-    line-height: 32px;
-  }
+  @media screen and ((max-width: 780px) or (max-height: 600px)) {
+    .title {
+      font-size: 1.5rem;
+      line-height: 32px;
+    }
 
-  .tangible-sync-style .subtitle {
-    font-size: 0.94rem;
+    .subtitle {
+      font-size: 0.94rem;
+    }
   }
-}
 </style>
 
 <div id="headerContainer"
     style$="--theme-frame-color:[[profileThemeInfo.themeFrameColor]];
             --theme-text-color:[[profileThemeInfo.themeFrameTextColor]];">
+  <!-- TODO(crbug.com/1442940): remove theme info across the profile picker -->
   <cr-icon-button id="backButton" iron-icon="cr:arrow-back"
       on-click="onBackClick_" aria-label$="[[getBackButtonAriaLabel_()]]"
       title$="[[getBackButtonAriaLabel_()]]"
@@ -119,11 +115,11 @@
     <img class="avatar" alt="" src="[[profileThemeInfo.themeGenericAvatar]]">
   </div>
 </div>
-<div class$="title-container [[getTangibleSyncStyleClass_()]]">
+<div class="title-container">
   <h1 class="title">$i18n{profileTypeChoiceTitle}</h1>
   <div class="subtitle">$i18n{profileTypeChoiceSubtitle}</div>
 </div>
-<div id="actionContainer" class$="[[getTangibleSyncStyleClass_()]]">
+<div id="actionContainer">
   <cr-button id="signInButton" class="action-button" on-click="onSignInClick_"
       disabled="[[profileCreationInProgress]]">
     $i18n{signInButtonLabel}
diff --git a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts
index c80604a..dfa56e2c 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts
@@ -58,13 +58,6 @@
         notify: true,
       },
 
-      isTangibleSyncEnabled_: {
-        type: Boolean,
-        value() {
-          return loadTimeData.getBoolean('isTangibleSyncEnabled');
-        },
-      },
-
       /**
        * The disclaimer for managed devices.
        */
@@ -93,7 +86,6 @@
   private managedDeviceDisclaimer_: boolean;
   private manageProfilesBrowserProxy_: ManageProfilesBrowserProxy =
       ManageProfilesBrowserProxyImpl.getInstance();
-  private isTangibleSyncEnabled_: boolean;
 
   // <if expr="chromeos_lacros">
   private hasAvailableAccounts_: boolean;
@@ -160,10 +152,6 @@
         'backButtonAriaLabel', this.i18n('profileTypeChoiceTitle'));
   }
 
-  private getTangibleSyncStyleClass_() {
-    return this.isTangibleSyncEnabled_ ? 'tangible-sync-style' : '';
-  }
-
   // <if expr="chromeos_lacros">
   private handleAvailableAccountsChanged_(availableAccounts:
                                               AvailableAccount[]) {
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_app.html b/chrome/browser/resources/signin/profile_picker/profile_picker_app.html
index 3bd800a..3dc15139 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_app.html
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_app.html
@@ -5,38 +5,22 @@
     margin: 0;
   }
 
+  cr-view-manager > [hidden] {
+    display: none;
+  }
+
   cr-view-manager > [slot='view'] {
     min-height: var(--view-min-size);
     min-width: var(--view-min-size);
   }
 
-  .left-banner {
-    content: url(images/left_banner_image.svg);
-    left: 0;
-  }
-
-  .right-banner {
-    content: url(images/right_banner_image.svg);
-    right: 0;
-  }
-
-  .left-banner,
-  .right-banner {
-    height: var(--banner-img-height);
-    overflow: hidden;
-    position: absolute;
-    top: 0;
-    width: var(--banner-img-width);
-    z-index: -1;
-  }
-
   .tangible-sync-style-left-banner {
-    content: url(images/tangible_sync_style_left_banner.svg);
+    content: url(images/left_banner.svg);
     left: 0;
   }
 
   .tangible-sync-style-right-banner {
-    content: url(images/tangible_sync_style_right_banner.svg);
+    content: url(images/right_banner.svg);
     right: 0;
   }
 
@@ -58,29 +42,18 @@
     }
 
     .tangible-sync-style-left-banner {
-      content: url(images/tangible_sync_style_left_banner_dark.svg);
+      content: url(images/left_banner_dark.svg);
     }
 
     .tangible-sync-style-right-banner {
-      content: url(images/tangible_sync_style_right_banner_dark.svg);
+      content: url(images/right_banner_dark.svg);
     }
 }
 </style>
-<img class="left-banner" alt=""
-  style$="display: [[getDisplayForVerticalBanner_(
-      shouldDisplayVerticalBanners_, isTangibleSyncEnabled_)]]">
-<img class="right-banner" alt=""
-    style$="display: [[getDisplayForVerticalBanner_(
-    shouldDisplayVerticalBanners_,
-    isTangibleSyncEnabled_)]]">
 <img class="tangible-sync-style-left-banner" alt=""
-    style$="display: [[getDisplayForTangibleSyncStyleVerticalBanner_(
-    shouldDisplayVerticalBanners_,
-    isTangibleSyncEnabled_)]]">
+    hidden="[[!shouldDisplayVerticalBanners_]]">
 <img class="tangible-sync-style-right-banner" alt=""
-    style$="display: [[getDisplayForTangibleSyncStyleVerticalBanner_(
-    shouldDisplayVerticalBanners_,
-    isTangibleSyncEnabled_)]]">
+    hidden="[[!shouldDisplayVerticalBanners_]]">
 
 <cr-view-manager id="viewManager">
   <profile-picker-main-view id="mainView" slot="view">
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts b/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts
index 4138be7..58d6be2 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts
@@ -70,11 +70,6 @@
             loadTimeData.getBoolean('isLocalProfileCreationDialogEnabled'),
       },
 
-      isTangibleSyncEnabled_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('isTangibleSyncEnabled'),
-      },
-
       shouldDisplayVerticalBanners_: {
         type: Boolean,
         value: false,
@@ -85,7 +80,6 @@
   newProfileThemeInfo: AutogeneratedThemeColorInfo;
   profileCreationInProgress: boolean;
   private isLocalProfileCreationDialogEnabled_: boolean;
-  private isTangibleSyncEnabled_: boolean;
   private shouldDisplayVerticalBanners_: boolean;
   private currentRoute_: Routes|null = null;
   private manageProfilesBrowserProxy_: ManageProfilesBrowserProxy =
@@ -181,20 +175,6 @@
     }
   }
 
-  private getDisplayForVerticalBanner_() {
-    if (!this.shouldDisplayVerticalBanners_) {
-      return 'none';
-    }
-    return this.isTangibleSyncEnabled_ ? 'none' : 'block';
-  }
-
-  private getDisplayForTangibleSyncStyleVerticalBanner_() {
-    if (!this.shouldDisplayVerticalBanners_) {
-      return 'none';
-    }
-    return this.isTangibleSyncEnabled_ ? 'block' : 'none';
-  }
-
   private getDocumentTitle_(step: string): string {
     switch (step) {
       case 'mainView':
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.html b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.html
index 29c6e4b..1f248aa 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.html
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.html
@@ -12,11 +12,15 @@
   }
 
   .title-container {
+    /* TODO(crbug.com/1441029): dedupe with |--tangible-sync-style-banner-width|
+     * from |tangible_sync_style_shared.css| */
+    --banner-width: 17vw;
+
     flex: 0 1 auto;
     margin: 30px auto 0 auto;
     max-width: 490px;
-    padding-inline-end: calc(var(--banner-img-width) - 10px);
-    padding-inline-start: calc(var(--banner-img-width) - 10px);
+    padding-inline-end: var(--banner-width);
+    padding-inline-start: var(--banner-width);
     text-align: center;
   }
 
@@ -137,7 +141,7 @@
 </style>
 
 <div class="flex-container">
-  <div class$="title-container [[getTangibleSyncStyleClass_()]]">
+  <div class="title-container">
     <img id="product-logo" on-click="onProductLogoClick_"
         srcset="chrome://theme/current-channel-logo@1x 1x,
                 chrome://theme/current-channel-logo@2x 2x"
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
index 0bfb228..0f0d5484 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
@@ -74,13 +74,6 @@
           return loadTimeData.getBoolean('askOnStartup');
         },
       },
-
-      isTangibleSyncEnabled_: {
-        type: Boolean,
-        value() {
-          return loadTimeData.getBoolean('isTangibleSyncEnabled');
-        },
-      },
     };
   }
 
@@ -92,7 +85,6 @@
       ManageProfilesBrowserProxyImpl.getInstance();
   private resizeObserver_: ResizeObserver|null = null;
   private previousRoute_: Routes|null = null;
-  private isTangibleSyncEnabled_: boolean;
 
   override ready() {
     super.ready();
@@ -206,10 +198,6 @@
     return !isAskOnStartupAllowed() || !this.profilesList_ ||
         this.profilesList_.length < 2;
   }
-
-  private getTangibleSyncStyleClass_() {
-    return this.isTangibleSyncEnabled_ ? 'tangible-sync-style' : '';
-  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_shared.css b/chrome/browser/resources/signin/profile_picker/profile_picker_shared.css
index a8c9847..073c6f5 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_shared.css
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_shared.css
@@ -10,8 +10,6 @@
  * #css_wrapper_metadata_end */
 
 :host {
-  --banner-img-height: 400px;
-  --banner-img-width: 169px;
   --profile-card-avatar-icon-size: 74px;
   --text-font-size: 1.16em;
   --scrollbar-width: 4px;
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
index a0e5629..341bf7e 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
@@ -213,6 +213,14 @@
       ->set_printer_name(printer_name);
 }
 
+void BinaryUploadService::Request::set_printer_type(
+    enterprise_connectors::ContentMetaData::PrintMetadata::PrinterType
+        printer_type) {
+  content_analysis_request_.mutable_request_data()
+      ->mutable_print_metadata()
+      ->set_printer_type(printer_type);
+}
+
 std::string BinaryUploadService::Request::SetRandomRequestToken() {
   DCHECK(request_token().empty());
 
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
index 7c3ccb9..c459f9bc 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
@@ -169,6 +169,9 @@
     void set_user_action_requests_count(uint64_t user_action_requests_count);
     void set_tab_url(const GURL& tab_url);
     void set_printer_name(const std::string& printer_name);
+    void set_printer_type(
+        enterprise_connectors::ContentMetaData::PrintMetadata::PrinterType
+            printer_type);
 
     std::string SetRandomRequestToken();
 
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
index 1795c2f7..4713fb2 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
@@ -649,6 +649,12 @@
     enterprise_connectors::RunSavePackageScanningCallback(download, false);
   }
 
+  // We can't safely return a verdict for this download because it's already
+  // been destroyed, so reset the callback here. We still need to run
+  // `FinishRequest` to notify the DownloadProtectionService that this deep scan
+  // has finished.
+  callback_.Reset();
+
   FinishRequest(DownloadCheckResult::UNKNOWN);
 }
 
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.cc b/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.cc
index ce8a802db..4e7e0b0 100644
--- a/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.cc
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.cc
@@ -6,10 +6,11 @@
 
 #include <memory>
 
+#include "base/debug/dump_without_crashing.h"
 #include "base/functional/bind.h"
 #include "base/time/time.h"
 #include "chrome/browser/signin/bound_session_credentials/bound_session_cookie_observer.h"
-#include "chrome/browser/signin/bound_session_credentials/fake_bound_session_refresh_cookie_fetcher.h"
+#include "chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h"
 #include "components/signin/public/base/signin_client.h"
 #include "net/http/http_status_code.h"
 
@@ -39,7 +40,7 @@
 
 void BoundSessionCookieControllerImpl::OnRequestBlockedOnCookie(
     base::OnceClosure resume_blocked_request) {
-  if (cookie_expiration_time_ > base::Time::Now()) {
+  if (IsCookieFresh()) {
     // Cookie is fresh.
     std::move(resume_blocked_request).Run();
     return;
@@ -58,7 +59,7 @@
   // TODO(b/263264391): Subtract a safety margin (e.g 2 seconds) from the cookie
   // expiration time.
   cookie_expiration_time_ = expiration_time;
-  if (cookie_expiration_time_ > base::Time::Now()) {
+  if (IsCookieFresh()) {
     ResumeBlockedRequests();
   }
   delegate_->OnCookieExpirationDateChanged();
@@ -66,19 +67,16 @@
 
 std::unique_ptr<BoundSessionRefreshCookieFetcher>
 BoundSessionCookieControllerImpl::CreateRefreshCookieFetcher() const {
-  // TODO(b/273920907): Replace with `BoundSessionRefreshCookieFetcherImpl` once
-  // implemented.
-  constexpr base::TimeDelta kFakeNetworkRequestEquivalentDelay(
-      base::Milliseconds(100));
   return refresh_cookie_fetcher_factory_for_testing_.is_null()
-             ? std::make_unique<FakeBoundSessionRefreshCookieFetcher>(
-                   client_, url_, cookie_name_,
-                   /*unlock_automatically_in=*/
-                   kFakeNetworkRequestEquivalentDelay)
+             ? std::make_unique<BoundSessionRefreshCookieFetcherImpl>(client_)
              : refresh_cookie_fetcher_factory_for_testing_.Run(client_, url_,
                                                                cookie_name_);
 }
 
+bool BoundSessionCookieControllerImpl::IsCookieFresh() {
+  return cookie_expiration_time() > base::Time::Now();
+}
+
 void BoundSessionCookieControllerImpl::MaybeRefreshCookie() {
   if (refresh_cookie_fetcher_) {
     return;
@@ -95,14 +93,18 @@
 void BoundSessionCookieControllerImpl::OnCookieRefreshFetched(
     BoundSessionRefreshCookieFetcher::Result result) {
   refresh_cookie_fetcher_.reset();
-  if (result.net_error == net::OK && result.response_code.has_value() &&
-      result.response_code.value() == net::HTTP_OK) {
-    // Requests are resumed once the cookie is set in the cookie jar. The cookie
-    // is expected to be fresh and `this` is notified with its expiration date
-    // before `OnCookieRefreshFetched` is called.
-    if (cookie_expiration_time_ > base::Time::Now()) {
+  if (result.net_error == net::OK && result.response_code == net::HTTP_OK) {
+    // Requests are resumed once the cookie is set in the cookie jar. The
+    // cookie is expected to be fresh and `this` is notified with its
+    // expiration date before `OnCookieRefreshFetched` is called.
+    if (IsCookieFresh()) {
       CHECK(resume_blocked_requests_.empty());
       return;
+    } else {
+      // The request should include `Set-Cookie` header. `this` is expected to
+      // have been notified of the new cookie inserted in the cookie jar by the
+      // time `OnCookieRefreshFetched()` is called.
+      base::debug::DumpWithoutCrashing();
     }
   }
   // TODO(b/263263352): Handle error cases.
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.h b/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.h
index 21e189ed..9efa8c10 100644
--- a/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.h
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl.h
@@ -54,6 +54,7 @@
   std::unique_ptr<BoundSessionRefreshCookieFetcher> CreateRefreshCookieFetcher()
       const;
 
+  bool IsCookieFresh();
   void MaybeRefreshCookie();
   void SetCookieExpirationTimeAndNotify(base::Time expiration_time);
   void OnCookieRefreshFetched(BoundSessionRefreshCookieFetcher::Result result);
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher.h b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher.h
index 2f2f61de..db62988 100644
--- a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher.h
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SIGNIN_BOUND_SESSION_CREDENTIALS_BOUND_SESSION_REFRESH_COOKIE_FETCHER_H_
 #define CHROME_BROWSER_SIGNIN_BOUND_SESSION_CREDENTIALS_BOUND_SESSION_REFRESH_COOKIE_FETCHER_H_
 
+#include "base/functional/callback_forward.h"
 #include "net/base/net_errors.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.cc b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.cc
new file mode 100644
index 0000000..46be6f06
--- /dev/null
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.cc
@@ -0,0 +1,108 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h"
+
+#include <memory>
+
+#include "components/signin/public/base/signin_client.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+BoundSessionRefreshCookieFetcherImpl::BoundSessionRefreshCookieFetcherImpl(
+    SigninClient* client)
+    : client_(client), url_loader_factory_(client->GetURLLoaderFactory()) {}
+
+BoundSessionRefreshCookieFetcherImpl::~BoundSessionRefreshCookieFetcherImpl() =
+    default;
+
+void BoundSessionRefreshCookieFetcherImpl::Start(
+    RefreshCookieCompleteCallback callback) {
+  CHECK(!callback_);
+  CHECK(callback);
+  callback_ = std::move(callback);
+  client_->DelayNetworkCall(
+      base::BindOnce(&BoundSessionRefreshCookieFetcherImpl::StartRefreshRequest,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BoundSessionRefreshCookieFetcherImpl::StartRefreshRequest() {
+  // TODO(b/273920907): Update the `traffic_annotation` setting once a mechanism
+  // allowing the user to disable the feature is implemented.
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("gaia_auth_rotate_bound_cookies",
+                                          R"(
+        semantics {
+          sender: "Chrome - Google authentication API"
+          description:
+            "This request is used to rotate bound Google authentication "
+            "cookies."
+          trigger:
+            "This request is triggered in a bound session when the bound Google"
+            " authentication cookies are soon to expire."
+          user_data {
+            type: ACCESS_TOKEN
+          }
+          data: "Request includes cookies and a signed token proving that a"
+                " request comes from the same device as was registered before."
+          destination: GOOGLE_OWNED_SERVICE
+          internal {
+            contacts {
+                email: "chrome-signin-team@google.com"
+            }
+          }
+          last_reviewed: "2023-05-09"
+        }
+        policy {
+          cookies_allowed: YES
+          cookies_store: "user"
+          setting:
+            "This feature is under development and must be enabled by user"
+            " action."
+          policy_exception_justification:
+            "Not implemented. "
+            "If the feature is on, this request must be made to ensure the user"
+            " maintains their signed in status on the web for Google owned"
+            " domains."
+        })");
+
+  auto request = std::make_unique<network::ResourceRequest>();
+  request->url = GaiaUrls::GetInstance()->rotate_bound_cookies_url();
+  request->method = "GET";
+
+  url::Origin origin = GaiaUrls::GetInstance()->gaia_origin();
+  request->site_for_cookies = net::SiteForCookies::FromOrigin(origin);
+  request->trusted_params = network::ResourceRequest::TrustedParams();
+  request->trusted_params->isolation_info =
+      net::IsolationInfo::CreateForInternalRequest(origin);
+
+  // TODO(b/273920907): Figure out how to handle redirects. Currently
+  // `network::SimpleURLLoader::SetOnRedirectCallback()` doesn't support
+  // modifying the headers nor asynchronously resuming the reguest.
+  url_loader_ =
+      network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
+  url_loader_->SetRetryOptions(
+      3, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
+  // TODO(b/273920907): Download the response body to support in refresh DBSC
+  // instructions update.
+  // `base::Unretained(this)` is safe as `this` owns `url_loader_`.
+  url_loader_->DownloadHeadersOnly(
+      url_loader_factory_.get(),
+      base::BindOnce(&BoundSessionRefreshCookieFetcherImpl::OnURLLoaderComplete,
+                     base::Unretained(this)));
+}
+
+void BoundSessionRefreshCookieFetcherImpl::OnURLLoaderComplete(
+    scoped_refptr<net::HttpResponseHeaders> headers) {
+  net::Error net_error = static_cast<net::Error>(url_loader_->NetError());
+
+  std::move(callback_).Run(
+      Result(net_error, headers ? absl::optional<int>(headers->response_code())
+                                : absl::nullopt));
+}
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h
new file mode 100644
index 0000000..733ffba
--- /dev/null
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h
@@ -0,0 +1,47 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SIGNIN_BOUND_SESSION_CREDENTIALS_BOUND_SESSION_REFRESH_COOKIE_FETCHER_IMPL_H_
+#define CHROME_BROWSER_SIGNIN_BOUND_SESSION_CREDENTIALS_BOUND_SESSION_REFRESH_COOKIE_FETCHER_IMPL_H_
+
+#include "chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher.h"
+
+#include <memory>
+
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+namespace network {
+class SimpleURLLoader;
+class SharedURLLoaderFactory;
+}  // namespace network
+
+class SigninClient;
+
+class BoundSessionRefreshCookieFetcherImpl
+    : public BoundSessionRefreshCookieFetcher {
+ public:
+  explicit BoundSessionRefreshCookieFetcherImpl(SigninClient* client);
+  ~BoundSessionRefreshCookieFetcherImpl() override;
+
+  // BoundSessionRefreshCookieFetcher:
+  void Start(RefreshCookieCompleteCallback callback) override;
+
+ private:
+  void StartRefreshRequest();
+  void OnURLLoaderComplete(scoped_refptr<net::HttpResponseHeaders> headers);
+
+  const raw_ptr<SigninClient> client_;
+  const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  RefreshCookieCompleteCallback callback_;
+
+  // Non-null after a fetch has started.
+  std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+  base::WeakPtrFactory<BoundSessionRefreshCookieFetcherImpl> weak_ptr_factory_{
+      this};
+};
+#endif  // CHROME_BROWSER_SIGNIN_BOUND_SESSION_CREDENTIALS_BOUND_SESSION_REFRESH_COOKIE_FETCHER_IMPL_H_
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc
new file mode 100644
index 0000000..d018592
--- /dev/null
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.h"
+
+#include <memory>
+#include <string>
+
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "base/time/time.h"
+#include "components/signin/public/base/test_signin_client.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/mojom/fetch_api.mojom-shared.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class BoundSessionRefreshCookieFetcherImplTest : public ::testing::Test {
+ public:
+  BoundSessionRefreshCookieFetcherImplTest() {
+    test_url_loader_factory_ = signin_client_.GetTestURLLoaderFactory();
+    fetcher_ =
+        std::make_unique<BoundSessionRefreshCookieFetcherImpl>(&signin_client_);
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  sync_preferences::TestingPrefServiceSyncable prefs_;
+  TestSigninClient signin_client_{&prefs_};
+  raw_ptr<network::TestURLLoaderFactory> test_url_loader_factory_ = nullptr;
+  std::unique_ptr<BoundSessionRefreshCookieFetcherImpl> fetcher_;
+};
+
+TEST_F(BoundSessionRefreshCookieFetcherImplTest, Success) {
+  EXPECT_FALSE(signin_client_.AreNetworkCallsDelayed());
+  base::test::TestFuture<BoundSessionRefreshCookieFetcher::Result> future;
+  fetcher_->Start(future.GetCallback());
+
+  EXPECT_EQ(test_url_loader_factory_->total_requests(), 1u);
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      test_url_loader_factory_->GetPendingRequest(0);
+  EXPECT_EQ(pending_request->request.url,
+            "https://accounts.google.com/RotateBoundCookies");
+  EXPECT_EQ(pending_request->request.method, "GET");
+  EXPECT_EQ(pending_request->request.credentials_mode,
+            network::mojom::CredentialsMode::kInclude);
+
+  test_url_loader_factory_->SimulateResponseForPendingRequest(
+      pending_request->request.url.spec(), "");
+  EXPECT_TRUE(future.Wait());
+  BoundSessionRefreshCookieFetcher::Result result = future.Get();
+  EXPECT_EQ(result.net_error, net::OK);
+  EXPECT_EQ(result.response_code, net::HTTP_OK);
+}
+
+TEST_F(BoundSessionRefreshCookieFetcherImplTest, FailureNetError) {
+  EXPECT_FALSE(signin_client_.AreNetworkCallsDelayed());
+  base::test::TestFuture<BoundSessionRefreshCookieFetcher::Result> future;
+  fetcher_->Start(future.GetCallback());
+
+  EXPECT_EQ(test_url_loader_factory_->total_requests(), 1u);
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      test_url_loader_factory_->GetPendingRequest(0);
+
+  network::URLLoaderCompletionStatus status(net::ERR_UNEXPECTED);
+  test_url_loader_factory_->SimulateResponseForPendingRequest(
+      pending_request->request.url, status,
+      network::mojom::URLResponseHead::New(), std::string());
+
+  EXPECT_TRUE(future.Wait());
+  BoundSessionRefreshCookieFetcher::Result result = future.Get();
+  EXPECT_EQ(result.net_error, status.error_code);
+  EXPECT_FALSE(result.response_code.has_value());
+}
+
+TEST_F(BoundSessionRefreshCookieFetcherImplTest, FailureHttpError) {
+  EXPECT_FALSE(signin_client_.AreNetworkCallsDelayed());
+  base::test::TestFuture<BoundSessionRefreshCookieFetcher::Result> future;
+  fetcher_->Start(future.GetCallback());
+
+  EXPECT_EQ(test_url_loader_factory_->total_requests(), 1u);
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      test_url_loader_factory_->GetPendingRequest(0);
+
+  test_url_loader_factory_->SimulateResponseForPendingRequest(
+      pending_request->request.url.spec(), "", net::HTTP_UNAUTHORIZED);
+
+  EXPECT_TRUE(future.Wait());
+  BoundSessionRefreshCookieFetcher::Result result = future.Get();
+  EXPECT_EQ(result.net_error, net::ERR_HTTP_RESPONSE_CODE_FAILURE);
+  EXPECT_EQ(result.response_code, net::HTTP_UNAUTHORIZED);
+}
+
+TEST_F(BoundSessionRefreshCookieFetcherImplTest, NetworkDelayed) {
+  signin_client_.SetNetworkCallsDelayed(true);
+  base::test::TestFuture<BoundSessionRefreshCookieFetcher::Result> future;
+  fetcher_->Start(future.GetCallback());
+  EXPECT_EQ(test_url_loader_factory_->total_requests(), 0u);
+
+  signin_client_.SetNetworkCallsDelayed(false);
+  EXPECT_EQ(test_url_loader_factory_->total_requests(), 1u);
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      test_url_loader_factory_->GetPendingRequest(0);
+  EXPECT_EQ(pending_request->request.url,
+            "https://accounts.google.com/RotateBoundCookies");
+  test_url_loader_factory_->SimulateResponseForPendingRequest(
+      pending_request->request.url.spec(), "");
+
+  EXPECT_TRUE(future.Wait());
+}
+
+}  // namespace
diff --git a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/FREMobileIdentityConsistencyFieldTrial.java b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/FREMobileIdentityConsistencyFieldTrial.java
index 619136c6..36d4a02a 100644
--- a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/FREMobileIdentityConsistencyFieldTrial.java
+++ b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/FREMobileIdentityConsistencyFieldTrial.java
@@ -227,15 +227,17 @@
     @VariationsGroup
     private static int generateFirstRunStringVariationsGroup(
             int lowEntropyValue, int lowEntropySize) {
-        int variationsPercentage = 0;
+        double variationsPercentage = 0;
         switch (VersionConstants.CHANNEL) {
             case Channel.DEFAULT:
             case Channel.CANARY:
             case Channel.DEV:
+            case Channel.BETA:
                 variationsPercentage = 10;
                 break;
-            case Channel.BETA:
             case Channel.STABLE:
+                variationsPercentage = 0.5;
+                break;
         }
         // For A/B testing all experiment groups should have the same percentages.
         assert variationsPercentage * VariationsGroup.MAX_VALUE <= 100;
diff --git a/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc b/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
index abd92de3..ea3cd13 100644
--- a/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
+++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
@@ -124,25 +124,64 @@
 ash::ServerBasedRecognitionAvailability
 SpeechRecognitionRecognizerClientImpl::GetServerBasedRecognitionAvailability(
     const std::string& language) {
-  if (!ash::features::IsInternalServerSideSpeechRecognitionEnabled()) {
+  if (!(ash::features::IsInternalServerSideSpeechRecognitionEnabled() ||
+        ash::features::IsInternalServerSideSpeechRecognitionEnabledByFinch())) {
     return ash::ServerBasedRecognitionAvailability::
         kServerBasedRecognitionNotAvailable;
   }
 
-  // This is an explicit list of locales that we support in addition to the list
-  // of languages below.
-  static constexpr auto kSupportedLocales =
-      base::MakeFixedFlatSet<base::StringPiece>({"ar-x-maghrebi", "zh-tw"});
+  static constexpr auto kSupportedLanguagesAndLocales =
+      base::MakeFixedFlatSet<base::StringPiece>({
+          "de",              // German
+          "de-AT",           // German (Austria)
+          "de-CH",           // German (Switzerland)
+          "de-DE",           // German (Germany)
+          "de-LI",           // German (Italy)
+          "en",              // English
+          "en-AU",           // English (Australia)
+          "en-CA",           // English (Canada)
+          "en-GB",           // English (UK)
+          "en-GB-oxendict",  // English (UK, OED spelling)
+          "en-IE",           // English (Ireland)
+          "en-NZ",           // English (New Zealand)
+          "en-US",           // English (US)
+          "en-XA",           // Long strings Pseudolocale
+          "en-ZA",           // English (South Africa)
+          "es",              // Spanish
+          "es-419",          // Spanish (Latin America)
+          "es-AR",           // Spanish (Argentina)
+          "es-CL",           // Spanish (Chile)
+          "es-CO",           // Spanish (Colombia)
+          "es-CR",           // Spanish (Costa Rica)
+          "es-ES",           // Spanish (Spain)
+          "es-HN",           // Spanish (Honduras)
+          "es-MX",           // Spanish (Mexico)
+          "es-PE",           // Spanish (Peru)
+          "es-US",           // Spanish (US)
+          "es-UY",           // Spanish (Uruguay)
+          "es-VE",           // Spanish (Venezuela)
+          "fr",              // French
+          "fr-CA",           // French (Canada)
+          "fr-CH",           // French (Switzerland)
+          "fr-FR",           // French (France)
+          "it",              // Italian
+          "it-CH",           // Italian (Switzerland)
+          "it-IT",           // Italian (Italy)
+          "ja",              // Japanese
+          "ko",              // Korean
+          "pt",              // Portuguese
+          "pt-BR",           // Portuguese (Brazil)
+          "pt-PT",           // Portuguese (Portugal)
+          "sv",              // Swedish
+          "tr",              // Turkish
+      });
 
-  // All locales under the following languages are supported. The locales are
-  // automatically routed to their appropriate recognizer on the server side.
-  static constexpr auto kSupportedLangauges =
-      base::MakeFixedFlatSet<base::StringPiece>(
-          {"cs", "da", "de", "en", "es", "fi", "fr", "hi", "id", "it", "ja",
-           "ko", "nl", "pt", "ru", "sv", "tr", "vi"});
+  bool is_supported =
+      ash::features::IsInternalServerSideSpeechRecognitionEnabled() &&
+      kSupportedLanguagesAndLocales.contains(language);
 
-  if (kSupportedLangauges.contains(language::ExtractBaseLanguage(language)) ||
-      kSupportedLocales.contains(base::ToLowerASCII(language))) {
+  if (is_supported ||
+      ash::features::IsInternalServerSideSpeechRecognitionEnabledByFinch()) {
     return ash::ServerBasedRecognitionAvailability::kAvailable;
   }
 
diff --git a/chrome/browser/ssl/https_only_mode_tab_helper.h b/chrome/browser/ssl/https_only_mode_tab_helper.h
index fc3f8813..9bafdd1 100644
--- a/chrome/browser/ssl/https_only_mode_tab_helper.h
+++ b/chrome/browser/ssl/https_only_mode_tab_helper.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SSL_HTTPS_ONLY_MODE_TAB_HELPER_H_
 #define CHROME_BROWSER_SSL_HTTPS_ONLY_MODE_TAB_HELPER_H_
 
+#include "base/containers/contains.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
@@ -44,7 +45,7 @@
   GURL fallback_url() const { return fallback_url_; }
 
   bool has_failed_upgrade(const GURL& url) {
-    return failed_upgrade_urls_.contains(url);
+    return base::Contains(failed_upgrade_urls_, url);
   }
   void add_failed_upgrade(const GURL& url) { failed_upgrade_urls_.insert(url); }
 
diff --git a/chrome/browser/supervised_user/android/supervised_user_web_content_handler_impl.h b/chrome/browser/supervised_user/android/supervised_user_web_content_handler_impl.h
index ff06d3f..a389ea7 100644
--- a/chrome/browser/supervised_user/android/supervised_user_web_content_handler_impl.h
+++ b/chrome/browser/supervised_user/android/supervised_user_web_content_handler_impl.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/supervised_user/chrome_supervised_user_web_content_handler_base.h"
 #include "components/supervised_user/core/browser/web_content_handler.h"
diff --git a/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc b/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc
index 0d34f35..02c621d 100644
--- a/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc
+++ b/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc
@@ -11,7 +11,7 @@
 #include "base/json/json_writer.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/string_util.h"
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -207,8 +207,8 @@
   resource_request->method = "POST";
   resource_request->headers.SetHeader(
       net::HttpRequestHeaders::kAuthorization,
-      base::StringPrintf(supervised_user::kAuthorizationHeaderFormat,
-                         token_info.token.c_str()));
+      base::JoinString(
+          {supervised_user::kAuthorizationHeader, token_info.token}, " "));
   (*it)->simple_url_loader = network::SimpleURLLoader::Create(
       std::move(resource_request), traffic_annotation);
   (*it)->simple_url_loader->AttachStringForUpload(body, "application/json");
diff --git a/chrome/browser/supervised_user/chromeos/supervised_user_web_content_handler_impl.h b/chrome/browser/supervised_user/chromeos/supervised_user_web_content_handler_impl.h
index 14f599d..0f575bf3 100644
--- a/chrome/browser/supervised_user/chromeos/supervised_user_web_content_handler_impl.h
+++ b/chrome/browser/supervised_user/chromeos/supervised_user_web_content_handler_impl.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/supervised_user/chrome_supervised_user_web_content_handler_base.h"
 #include "chromeos/crosapi/mojom/parent_access.mojom.h"
diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc
index 75cc36e0a..0fc3f01 100644
--- a/chrome/browser/sync/sync_ui_util.cc
+++ b/chrome/browser/sync/sync_ui_util.cc
@@ -362,7 +362,7 @@
   // transport mode so calling IsTrustedVaultKeyRequiredForPreferredDataTypes()
   // is enough.
   //
-  // WARNING: Must match PasswordModelTypeController::GetPreconditionState().
+  // WARNING: Must match CredentialModelTypeController::GetPreconditionState().
   return password_manager::features_util::IsOptedInForAccountStorage(
       pref_service, sync_service);
 }
@@ -390,7 +390,7 @@
   // (SyncUserSettingsImpl::IsEncryptedDatatypeEnabled() relies on the preferred
   // types).
   //
-  // WARNING: Must match PasswordModelTypeController::GetPreconditionState().
+  // WARNING: Must match CredentialModelTypeController::GetPreconditionState().
   return password_manager::features_util::IsOptedInForAccountStorage(
       pref_service, sync_service);
 }
diff --git a/chrome/browser/sync/test/integration/single_client_webauthn_credentials_sync_test.cc b/chrome/browser/sync/test/integration/single_client_webauthn_credentials_sync_test.cc
index 96ffb7f..e8a6b30 100644
--- a/chrome/browser/sync/test/integration/single_client_webauthn_credentials_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_webauthn_credentials_sync_test.cc
@@ -4,14 +4,17 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h"
+#include "chrome/browser/sync/test/integration/secondary_account_helper.h"
 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
 #include "chrome/browser/sync/test/integration/sync_integration_test_util.h"
 #include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/sync/test/integration/webauthn_credentials_helper.h"
 #include "chrome/browser/ui/browser.h"
+#include "components/password_manager/core/browser/password_manager_features_util.h"
 #include "components/sync/base/client_tag_hash.h"
 #include "components/sync/base/features.h"
+#include "components/sync/base/model_type.h"
 #include "components/sync/engine/loopback_server/loopback_server_entity.h"
 #include "components/sync/engine/loopback_server/persistent_unique_client_entity.h"
 #include "components/sync/protocol/entity_specifics.pb.h"
@@ -29,6 +32,7 @@
 using webauthn_credentials_helper::LocalPasskeysMatchChecker;
 using webauthn_credentials_helper::NewPasskey;
 using webauthn_credentials_helper::PasskeyHasSyncId;
+using webauthn_credentials_helper::PasskeySyncActiveChecker;
 using webauthn_credentials_helper::ServerPasskeysMatchChecker;
 
 constexpr int kSingleProfile = 0;
@@ -199,4 +203,38 @@
               testing::UnorderedElementsAreArray(expected_sync_ids));
 }
 
+// The unconsented primary account isn't supported on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+// Tests that passkeys sync on transport mode only if the user has consented to
+// showing credentials from their Google account.
+IN_PROC_BROWSER_TEST_F(SingleClientWebAuthnCredentialsSyncTest,
+                       TransportModeConsent) {
+  const std::string sync_id = InjectPasskeyToFakeServer(NewPasskey());
+  ASSERT_TRUE(SetupClients());
+
+  AccountInfo account_info = secondary_account_helper::SignInUnconsentedAccount(
+      GetProfile(0), &test_url_loader_factory_, "user@email.com");
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+  ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureEnabled());
+
+  // Passkeys should not be syncing.
+  EXPECT_FALSE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::WEBAUTHN_CREDENTIAL));
+
+  // Let the user opt in to transport mode and wait for passkeys to start
+  // syncing.
+  password_manager::features_util::OptInToAccountStorage(
+      GetProfile(0)->GetPrefs(), GetSyncService(0));
+  PasskeySyncActiveChecker(GetSyncService(0)).Wait();
+  EXPECT_TRUE(LocalPasskeysMatchChecker(kSingleProfile,
+                                        ElementsAre(PasskeyHasSyncId(sync_id)))
+                  .Wait());
+
+  // Opt out. The passkey should be removed.
+  password_manager::features_util::OptOutOfAccountStorageAndClearSettings(
+      GetProfile(0)->GetPrefs(), GetSyncService(0));
+  EXPECT_TRUE(LocalPasskeysMatchChecker(kSingleProfile, IsEmpty()).Wait());
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/webauthn_credentials_helper.cc b/chrome/browser/sync/test/integration/webauthn_credentials_helper.cc
index 137636b..ee28548 100644
--- a/chrome/browser/sync/test/integration/webauthn_credentials_helper.cc
+++ b/chrome/browser/sync/test/integration/webauthn_credentials_helper.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/sync/test/integration/sync_integration_test_util.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/webauthn/passkey_model_factory.h"
+#include "components/sync/base/model_type.h"
 #include "components/sync/protocol/sync_entity.pb.h"
 #include "components/sync/protocol/webauthn_credential_specifics.pb.h"
 #include "components/webauthn/core/browser/passkey_model.h"
@@ -43,6 +44,15 @@
 
 }  // namespace
 
+PasskeySyncActiveChecker::PasskeySyncActiveChecker(
+    syncer::SyncServiceImpl* service)
+    : SingleClientStatusChangeChecker(service) {}
+PasskeySyncActiveChecker::~PasskeySyncActiveChecker() = default;
+
+bool PasskeySyncActiveChecker::IsExitConditionSatisfied(std::ostream* os) {
+  return service()->GetActiveDataTypes().Has(syncer::WEBAUTHN_CREDENTIAL);
+}
+
 LocalPasskeysMatchChecker::LocalPasskeysMatchChecker(int profile,
                                                      Matcher matcher)
     : SingleClientStatusChangeChecker(test()->GetSyncService(profile)),
diff --git a/chrome/browser/sync/test/integration/webauthn_credentials_helper.h b/chrome/browser/sync/test/integration/webauthn_credentials_helper.h
index 75765e3..820c928 100644
--- a/chrome/browser/sync/test/integration/webauthn_credentials_helper.h
+++ b/chrome/browser/sync/test/integration/webauthn_credentials_helper.h
@@ -20,6 +20,16 @@
 
 namespace webauthn_credentials_helper {
 
+// Checker to wait until the WEBAUTHN_CREDENTIAL datatype becomes active.
+class PasskeySyncActiveChecker : public SingleClientStatusChangeChecker {
+ public:
+  explicit PasskeySyncActiveChecker(syncer::SyncServiceImpl* service);
+  ~PasskeySyncActiveChecker() override;
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied(std::ostream* os) override;
+};
+
 class LocalPasskeysMatchChecker : public SingleClientStatusChangeChecker {
  public:
   using Matcher =
diff --git a/chrome/browser/task_manager/providers/crosapi/crosapi_task_provider_ash.h b/chrome/browser/task_manager/providers/crosapi/crosapi_task_provider_ash.h
index faae6d0..9196318 100644
--- a/chrome/browser/task_manager/providers/crosapi/crosapi_task_provider_ash.h
+++ b/chrome/browser/task_manager/providers/crosapi/crosapi_task_provider_ash.h
@@ -7,6 +7,7 @@
 
 #include <unordered_map>
 
+#include "base/gtest_prod_util.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/crosapi/task_manager_ash.h"
 #include "chrome/browser/task_manager/providers/task_provider.h"
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate.cc
index f65d270..3a3183d4 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate.cc
@@ -230,7 +230,7 @@
 
   password_manager::metrics_util::LogFilledCredentialIsFromAndroidApp(
       credential.is_affiliation_based_match().value());
-  driver_->TouchToFillClosed(ShowVirtualKeyboard(false));
+  driver_->KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false));
 
   driver_->FillSuggestion(credential.username(), credential.password());
 
@@ -255,6 +255,7 @@
     TouchToFillOutcome outcome,
     bool show_virtual_keyboard) {
   std::exchange(driver_, nullptr)
-      ->TouchToFillClosed(ShowVirtualKeyboard(show_virtual_keyboard));
+      ->KeyboardReplacingSurfaceClosed(
+          ShowVirtualKeyboard(show_virtual_keyboard));
   base::UmaHistogramEnumeration("PasswordManager.TouchToFill.Outcome", outcome);
 }
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate_unittest.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate_unittest.cc
index ab304eb..ee8d88d 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate_unittest.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate_unittest.cc
@@ -81,7 +81,10 @@
               FillSuggestion,
               (const std::u16string&, const std::u16string&),
               (override));
-  MOCK_METHOD(void, TouchToFillClosed, (ShowVirtualKeyboard), (override));
+  MOCK_METHOD(void,
+              KeyboardReplacingSurfaceClosed,
+              (ShowVirtualKeyboard),
+              (override));
   MOCK_METHOD(void, TriggerFormSubmission, (), (override));
   MOCK_METHOD(const GURL&, GetLastCommittedURL, (), (const override));
 };
@@ -222,7 +225,8 @@
   // Test that we correctly log the absence of an Android credential.
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
                                        std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
   touch_to_fill_controller().OnCredentialSelected(credentials[0]);
   histogram_tester().ExpectUniqueSample(
       "PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
@@ -264,7 +268,8 @@
 
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
                                        std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
   EXPECT_CALL(driver(), TriggerFormSubmission());
   EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(Eq(u"alice")));
 
@@ -293,7 +298,8 @@
 
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
                                        std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
 
   EXPECT_CALL(driver(), TriggerFormSubmission()).Times(0);
   EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);
@@ -329,7 +335,8 @@
   EXPECT_CALL(driver(), TriggerFormSubmission()).Times(0);
   EXPECT_CALL(driver(),
               FillSuggestion(std::u16string(u""), std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
 
   touch_to_fill_controller().OnCredentialSelected(credentials[0]);
 }
@@ -360,7 +367,8 @@
   EXPECT_CALL(driver(), TriggerFormSubmission()).Times(0);
   EXPECT_CALL(driver(),
               FillSuggestion(std::u16string(u""), std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
 
   touch_to_fill_controller().OnCredentialSelected(credentials[0]);
 }
@@ -383,7 +391,8 @@
   // Test that we correctly log the absence of an Android credential.
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
                                        std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
 
   EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
       .WillOnce(Return(false));
@@ -420,7 +429,8 @@
 
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
                                        std::u16string(u"p4ssw0rd")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
 
   EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
       .WillOnce(Return(true));
@@ -451,7 +461,8 @@
           autofill::mojom::SubmissionReadinessState::kNoInformation));
 
   EXPECT_CALL(driver(), FillSuggestion(_, _)).Times(0);
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(true)));
 
   EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
       .WillOnce(Return(true));
@@ -528,7 +539,8 @@
   // Test that we correctly log the presence of an Android credential.
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"bob"),
                                        std::u16string(u"s3cr3t")));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
   EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
       .WillOnce(Return(false));
   touch_to_fill_controller().OnCredentialSelected(credentials[1]);
@@ -599,7 +611,8 @@
       MakeTouchToFillControllerDelegate(
           autofill::mojom::SubmissionReadinessState::kNoInformation));
 
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(true)));
   touch_to_fill_controller().OnDismiss();
 
   auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
@@ -634,7 +647,8 @@
       MakeTouchToFillControllerDelegate(
           autofill::mojom::SubmissionReadinessState::kNoInformation));
 
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
   EXPECT_CALL(client(),
               NavigateToManagePasswordsPage(
                   password_manager::ManagePasswordsReferrer::kTouchToFill));
@@ -705,7 +719,8 @@
 
   EXPECT_CALL(*webauthn_credentials_delegate(),
               SelectPasskey(base::Base64Encode(credential.credential_id())));
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
   touch_to_fill_controller().OnPasskeyCredentialSelected(credentials[0]);
   histogram_tester().ExpectUniqueSample(
       "PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
@@ -743,7 +758,8 @@
   touch_to_fill_controller().Show(
       credentials, {}, MakeTouchToFillControllerDelegate(submission_readiness));
 
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(false)));
   EXPECT_CALL(driver(),
               FillSuggestion(credential.username(), credential.password()));
   EXPECT_CALL(driver(), TriggerFormSubmission())
@@ -770,7 +786,8 @@
   touch_to_fill_controller().Show(
       credentials, {}, MakeTouchToFillControllerDelegate(submission_readiness));
 
-  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
+  EXPECT_CALL(driver(),
+              KeyboardReplacingSurfaceClosed(ShowVirtualKeyboard(true)));
   touch_to_fill_controller().OnDismiss();
 
   uma_recorder.ExpectUniqueSample(
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e1b3322d..8e156db 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -432,6 +432,7 @@
     "//chrome/browser/resources/usb_internals:resources",
     "//chrome/browser/safe_browsing",
     "//chrome/browser/share",
+    "//chrome/browser/ui/side_panel:side_panel_enums",
     "//chrome/browser/ui/webui:configs",
     "//chrome/browser/ui/webui/omnibox:mojo_bindings",
     "//chrome/browser/ui/webui/segmentation_internals:mojo_bindings",
@@ -446,7 +447,7 @@
     "//chrome/services/qrcode_generator/public/cpp",
     "//chrome/services/qrcode_generator/public/mojom",
     "//components/about_ui",
-    "//components/access_code_cast/common",
+    "//components/access_code_cast/common:metrics",
     "//components/account_id",
     "//components/autofill/content/browser:risk_proto",
     "//components/autofill/core/browser",
@@ -2376,6 +2377,8 @@
       "ash/game_dashboard/chrome_game_dashboard_delegate.h",
       "ash/glanceables/chrome_glanceables_delegate.cc",
       "ash/glanceables/chrome_glanceables_delegate.h",
+      "ash/glanceables/glanceables_classroom_client_impl.cc",
+      "ash/glanceables/glanceables_classroom_client_impl.h",
       "ash/glanceables/glanceables_keyed_service.cc",
       "ash/glanceables/glanceables_keyed_service.h",
       "ash/glanceables/glanceables_keyed_service_factory.cc",
@@ -3317,7 +3320,7 @@
       "//chrome/browser/enterprise/connectors/device_trust:prefs",
       "//chrome/browser/media/router/discovery/access_code:access_code_cast_feature",
       "//chrome/browser/media/router/discovery/access_code:discovery_resources_proto",
-      "//chrome/browser/metrics/structured",
+      "//chrome/browser/metrics/structured:features",
       "//chrome/browser/nearby_sharing:share_target",
       "//chrome/browser/nearby_sharing/certificates",
       "//chrome/browser/nearby_sharing/client",
@@ -4404,7 +4407,6 @@
       "side_panel/history_clusters/history_clusters_tab_helper.h",
       "side_panel/read_anything/read_anything_side_panel_controller_utils.h",
       "side_panel/side_panel_entry_id.h",
-      "side_panel/side_panel_open_trigger.h",
       "side_panel/side_panel_prefs.cc",
       "side_panel/side_panel_prefs.h",
       "views/send_tab_to_self/send_tab_to_self_bubble.cc",
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index a85529a..105e9f3 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -57,7 +57,6 @@
     "java/src/org/chromium/chrome/browser/omnibox/status/StatusView.java",
     "java/src/org/chromium/chrome/browser/omnibox/status/StatusViewBinder.java",
     "java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java",
-    "java/src/org/chromium/chrome/browser/omnibox/suggestions/ActionChipsDelegate.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerProvider.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java",
@@ -70,7 +69,6 @@
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/FaviconFetcher.java",
-    "java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownAdapter.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownEmbedder.java",
@@ -81,7 +79,6 @@
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListProperties.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionProcessor.java",
-    "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionsMetrics.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/UrlBarDelegate.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/AnswerSuggestionProcessor.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/AnswerSuggestionViewBinder.java",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index 95d4bd8..2f43e770 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -33,7 +33,6 @@
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator.PageInfoAction;
 import org.chromium.chrome.browser.omnibox.status.StatusView;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate;
@@ -50,6 +49,7 @@
 import org.chromium.chrome.browser.tabmodel.TabWindowManager;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.components.search_engines.TemplateUrlService;
 import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -163,7 +163,7 @@
             @NonNull BooleanSupplier isToolbarMicEnabledSupplier,
             @Nullable Supplier<MerchantTrustSignalsCoordinator>
                     merchantTrustSignalsCoordinatorSupplier,
-            @NonNull ActionChipsDelegate actionChipsDelegate,
+            @NonNull OmniboxActionDelegate omniboxActionDelegate,
             BrowserStateBrowserControlsVisibilityDelegate browserControlsVisibilityDelegate,
             Callback<Throwable> reportExceptionCallback,
             @Nullable BackPressManager backPressManager,
@@ -206,7 +206,7 @@
                 mOmniboxDropdownEmbedderImpl, mUrlCoordinator, modalDialogManagerSupplier,
                 activityTabSupplier, shareDelegateSupplier, locationBarDataProvider,
                 profileObservableSupplier, bringTabToFrontCallback, tabWindowManagerSupplier,
-                bookmarkState, actionChipsDelegate, omniboxSuggestionsDropdownScrollListener,
+                bookmarkState, omniboxActionDelegate, omniboxSuggestionsDropdownScrollListener,
                 openHistoryClustersDelegate);
         StatusView statusView = mLocationBarLayout.findViewById(R.id.location_bar_status);
         mStatusCoordinator = new StatusCoordinator(isTabletWindow(), statusView, mUrlCoordinator,
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java
index 3214b4bb..646d43c3 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java
@@ -43,6 +43,13 @@
                     ChromeFeatureList.OMNIBOX_MODERNIZE_VISUAL_UPDATE,
                     "modernize_visual_update_merge_clipboard_on_ntp", false);
 
+    public static final String TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING_PARAM =
+            "disable_toolbar_reordering";
+    public static final BooleanCachedFieldTrialParameter
+            TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING =
+                    new BooleanCachedFieldTrialParameter(ChromeFeatureList.TAB_STRIP_REDESIGN,
+                            TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING_PARAM, false);
+
     private static final MutableFlagWithSafeDefault sOmniboxConsumesImeInsets =
             new MutableFlagWithSafeDefault(ChromeFeatureList.OMNIBOX_CONSUMERS_IME_INSETS, false);
     private static final MutableFlagWithSafeDefault sShouldAdaptToNarrowTabletWindows =
@@ -71,6 +78,13 @@
                     ChromeFeatureList.OMNIBOX_WARM_RECYCLED_VIEW_POOL, false);
 
     /**
+     * @return Whether Toolbar reordering for tab strip redesign is disabled.
+     */
+    public static boolean isTabStripToolbarReorderingDisabled() {
+        return TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING.getValue();
+    }
+
+    /**
      * @param context The activity context.
      * @return Whether the new modernize visual UI update should be shown.
      */
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index c03bff1..3bd9977 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -16,7 +16,6 @@
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
-import android.text.TextWatcher;
 import android.text.style.ReplacementSpan;
 import android.util.AttributeSet;
 import android.view.GestureDetector;
@@ -32,7 +31,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.text.BidiFormatter;
-import androidx.core.util.ObjectsCompat;
 import androidx.core.view.inputmethod.EditorInfoCompat;
 
 import org.chromium.base.ApiCompatibilityUtils;
@@ -79,7 +77,6 @@
 
     private UrlBarDelegate mUrlBarDelegate;
     private UrlTextChangeListener mUrlTextChangeListener;
-    private TextWatcher mTextChangedListener;
     private UrlBarTextContextMenuDelegate mTextContextMenuDelegate;
     private Callback<Integer> mUrlDirectionListener;
 
@@ -272,7 +269,6 @@
         setOnFocusChangeListener(null);
         mTextContextMenuDelegate = null;
         mUrlTextChangeListener = null;
-        mTextChangedListener = null;
     }
 
     /**
@@ -565,21 +561,6 @@
     }
 
     /**
-     * Set the listener to be notified when the view's text has changed.
-     * @param textChangedListener The listener to be notified.
-     */
-    public void setTextChangedListener(TextWatcher textChangedListener) {
-        if (ObjectsCompat.equals(mTextChangedListener, textChangedListener)) {
-            return;
-        } else if (mTextChangedListener != null) {
-            removeTextChangedListener(mTextChangedListener);
-        }
-
-        mTextChangedListener = textChangedListener;
-        addTextChangedListener(mTextChangedListener);
-    }
-
-    /**
      * Set the text to report to Autofill services upon call to onProvideAutofillStructure.
      */
     public void setTextForAutofillServices(CharSequence text) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
index ae7d7545..cc2aa4f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.omnibox;
 
 import android.content.Context;
-import android.text.TextWatcher;
 import android.view.ActionMode;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
@@ -113,11 +112,6 @@
         mMediator.addUrlTextChangeListener(listener);
     }
 
-    /** @see TextWatcher */
-    public void addTextChangedListener(TextWatcher textWatcher) {
-        mMediator.addTextChangedListener(textWatcher);
-    }
-
     /** @see UrlBarMediator#setUrlBarData(UrlBarData, int, int) */
     public boolean setUrlBarData(
             UrlBarData data, @ScrollType int scrollType, @SelectionState int state) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
index 56deee4..4469cc1 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
@@ -8,10 +8,8 @@
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.net.Uri;
-import android.text.Editable;
 import android.text.Spanned;
 import android.text.TextUtils;
-import android.text.TextWatcher;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -35,8 +33,7 @@
 /**
  * Handles collecting and pushing state information to the UrlBar model.
  */
-class UrlBarMediator
-        implements UrlBar.UrlBarTextContextMenuDelegate, UrlBar.UrlTextChangeListener, TextWatcher {
+class UrlBarMediator implements UrlBar.UrlBarTextContextMenuDelegate, UrlBar.UrlTextChangeListener {
     private final PropertyModel mModel;
 
     private Callback<Boolean> mOnFocusChangeCallback;
@@ -47,7 +44,6 @@
     private @SelectionState int mSelectionState = UrlBarCoordinator.SelectionState.SELECT_ALL;
 
     private final List<UrlTextChangeListener> mUrlTextChangeListeners = new ArrayList<>();
-    private final List<TextWatcher> mTextChangedListeners = new ArrayList<>();
 
     /**
      * Creates a URLBarMediator.
@@ -65,13 +61,11 @@
         mModel.set(UrlBarProperties.SHOW_CURSOR, false);
         mModel.set(UrlBarProperties.TEXT_CONTEXT_MENU_DELEGATE, this);
         mModel.set(UrlBarProperties.URL_TEXT_CHANGE_LISTENER, this);
-        mModel.set(UrlBarProperties.TEXT_CHANGED_LISTENER, this);
         setBrandedColorScheme(BrandedColorScheme.APP_DEFAULT);
     }
 
     public void destroy() {
         mUrlTextChangeListeners.clear();
-        mTextChangedListeners.clear();
         mOnFocusChangeCallback = (unused) -> {};
     }
 
@@ -80,11 +74,6 @@
         mUrlTextChangeListeners.add(listener);
     }
 
-    /** @see android.widget.TextView#addTextChangedListener */
-    public void addTextChangedListener(TextWatcher textWatcher) {
-        mTextChangedListeners.add(textWatcher);
-    }
-
     /**
      * Updates the text content of the UrlBar.
      *
@@ -359,28 +348,4 @@
                     textWithoutAutocomplete, textWithAutocomplete);
         }
     }
-
-    /** @see TextWatcher */
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        for (int i = 0; i < mTextChangedListeners.size(); i++) {
-            mTextChangedListeners.get(i).beforeTextChanged(s, start, count, after);
-        }
-    }
-
-    /** @see TextWatcher */
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        for (int i = 0; i < mTextChangedListeners.size(); i++) {
-            mTextChangedListeners.get(i).onTextChanged(s, start, before, count);
-        }
-    }
-
-    /** @see TextWatcher */
-    @Override
-    public void afterTextChanged(Editable editable) {
-        for (int i = 0; i < mTextChangedListeners.size(); i++) {
-            mTextChangedListeners.get(i).afterTextChanged(editable);
-        }
-    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java
index a25f66a..6d1103a 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java
@@ -9,7 +9,6 @@
 import android.content.Context;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
-import android.text.TextWatcher;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -40,10 +39,6 @@
     @Mock
     UrlBar.UrlTextChangeListener mAnotherUrlTextMockListener;
     @Mock
-    TextWatcher mMockTextWatcher;
-    @Mock
-    TextWatcher mAnotherMockTextWatcher;
-    @Mock
     Callback<Boolean> mFocusChangeCallback;
 
     PropertyModel mModel;
@@ -280,20 +275,6 @@
                 .onTextChanged(text, textWithAutocomplete);
     }
 
-    @Test
-    public void textWatcherCompositeObserver() {
-        mMediator.addTextChangedListener(mMockTextWatcher);
-
-        CharSequence text = "foo";
-        mMediator.onTextChanged(text, 0, 1, 2);
-        Mockito.verify(mMockTextWatcher, Mockito.times(1)).onTextChanged(text, 0, 1, 2);
-
-        mMediator.addTextChangedListener(mAnotherMockTextWatcher);
-        mMediator.onTextChanged(text, 0, 1, 2);
-        Mockito.verify(mMockTextWatcher, Mockito.times(2)).onTextChanged(text, 0, 1, 2);
-        Mockito.verify(mAnotherMockTextWatcher, Mockito.times(1)).onTextChanged(text, 0, 1, 2);
-    }
-
     private static SpannableStringBuilder spannable(String text) {
         return new SpannableStringBuilder(text);
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
index 35862573..5beebc3 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.omnibox;
 
-import android.text.TextWatcher;
 import android.view.ActionMode;
 
 import org.chromium.base.Callback;
@@ -120,10 +119,6 @@
     public static final WritableObjectPropertyKey<UrlTextChangeListener> URL_TEXT_CHANGE_LISTENER =
             new WritableObjectPropertyKey<>();
 
-    /** The callback to be notified on text changes. @see TextWatcher. */
-    public static final WritableObjectPropertyKey<TextWatcher> TEXT_CHANGED_LISTENER =
-            new WritableObjectPropertyKey<>();
-
     /**
      * Specifies the color scheme. It can be light or dark because of a publisher defined color,
      * incognito, or the default theme that follows dynamic colors.
@@ -144,6 +139,6 @@
     public static final PropertyKey[] ALL_KEYS =
             new PropertyKey[] {ACTION_MODE_CALLBACK, ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE,
                     FOCUS_CHANGE_CALLBACK, SHOW_CURSOR, TEXT_CONTEXT_MENU_DELEGATE, TEXT_STATE,
-                    URL_DIRECTION_LISTENER, URL_TEXT_CHANGE_LISTENER, TEXT_CHANGED_LISTENER,
-                    BRANDED_COLOR_SCHEME, INCOGNITO_COLORS_ENABLED, WINDOW_DELEGATE};
+                    URL_DIRECTION_LISTENER, URL_TEXT_CHANGE_LISTENER, BRANDED_COLOR_SCHEME,
+                    INCOGNITO_COLORS_ENABLED, WINDOW_DELEGATE};
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
index d6927b7..08e2fe0 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
@@ -95,8 +95,6 @@
             view.setUrlDirectionListener(model.get(UrlBarProperties.URL_DIRECTION_LISTENER));
         } else if (UrlBarProperties.URL_TEXT_CHANGE_LISTENER.equals(propertyKey)) {
             view.setUrlTextChangeListener(model.get(UrlBarProperties.URL_TEXT_CHANGE_LISTENER));
-        } else if (UrlBarProperties.TEXT_CHANGED_LISTENER.equals(propertyKey)) {
-            view.setTextChangedListener(model.get(UrlBarProperties.TEXT_CHANGED_LISTENER));
         } else if (UrlBarProperties.WINDOW_DELEGATE.equals(propertyKey)) {
             view.setWindowDelegate(model.get(UrlBarProperties.WINDOW_DELEGATE));
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
index b27fda8..e467ad9 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.omnibox.styles;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.util.SparseArray;
@@ -146,15 +145,13 @@
      */
     public static @ColorInt int getUrlBarPrimaryTextColor(
             Context context, @BrandedColorScheme int brandedColorScheme) {
-        final Resources resources = context.getResources();
-        @ColorInt
-        int color;
+        final @ColorInt int color;
         if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
-            color = resources.getColor(R.color.branded_url_text_on_light_bg);
+            color = context.getColor(R.color.branded_url_text_on_light_bg);
         } else if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
-            color = resources.getColor(R.color.branded_url_text_on_dark_bg);
+            color = context.getColor(R.color.branded_url_text_on_dark_bg);
         } else if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
-            color = resources.getColor(R.color.url_bar_primary_text_incognito);
+            color = context.getColor(R.color.url_bar_primary_text_incognito);
         } else {
             color = MaterialColors.getColor(context, R.attr.colorOnSurface, TAG);
         }
@@ -170,15 +167,13 @@
      */
     public static @ColorInt int getUrlBarSecondaryTextColor(
             Context context, @BrandedColorScheme int brandedColorScheme) {
-        final Resources resources = context.getResources();
-        @ColorInt
-        int color;
+        final @ColorInt int color;
         if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
-            color = resources.getColor(R.color.branded_url_text_variant_on_light_bg);
+            color = context.getColor(R.color.branded_url_text_variant_on_light_bg);
         } else if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
-            color = resources.getColor(R.color.branded_url_text_variant_on_dark_bg);
+            color = context.getColor(R.color.branded_url_text_variant_on_dark_bg);
         } else if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
-            color = resources.getColor(R.color.url_bar_secondary_text_incognito);
+            color = context.getColor(R.color.url_bar_secondary_text_incognito);
         } else {
             color = MaterialColors.getColor(context, R.attr.colorOnSurfaceVariant, TAG);
         }
@@ -207,15 +202,16 @@
     public static @ColorInt int getUrlBarDangerColor(
             Context context, @BrandedColorScheme int brandedColorScheme) {
         // Danger color has semantic meaning and it doesn't change with dynamic colors.
-        @ColorRes
-        int colorId = R.color.default_red;
+        final @ColorRes int colorId;
         if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME
                 || brandedColorScheme == BrandedColorScheme.INCOGNITO) {
             colorId = R.color.default_red_light;
         } else if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
             colorId = R.color.default_red_dark;
+        } else {
+            colorId = R.color.default_red;
         }
-        return context.getResources().getColor(colorId);
+        return context.getColor(colorId);
     }
 
     /**
@@ -228,15 +224,16 @@
     public static @ColorInt int getUrlBarSecureColor(
             Context context, @BrandedColorScheme int brandedColorScheme) {
         // Secure color has semantic meaning and it doesn't change with dynamic colors.
-        @ColorRes
-        int colorId = R.color.default_green;
+        final @ColorRes int colorId;
         if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME
                 || brandedColorScheme == BrandedColorScheme.INCOGNITO) {
             colorId = R.color.default_green_light;
         } else if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
             colorId = R.color.default_green_dark;
+        } else {
+            colorId = R.color.default_green;
         }
-        return context.getResources().getColor(colorId);
+        return context.getColor(colorId);
     }
 
     /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/ActionChipsDelegate.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/ActionChipsDelegate.java
deleted file mode 100644
index afab190..0000000
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/ActionChipsDelegate.java
+++ /dev/null
@@ -1,20 +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.chrome.browser.omnibox.suggestions;
-
-import org.chromium.components.omnibox.action.OmniboxAction;
-
-/**
- * An interface for handling interactions for Omnibox Action Chips.
- * TODO(crbug/1418077): repurpose as a OmniboxActionFactory.
- */
-public interface ActionChipsDelegate {
-    /**
-     * Call this method when the pedal is clicked.
-     *
-     * @param action the {@link OmniboxAction} whose action we want to execute.
-     */
-    void execute(OmniboxAction action);
-}
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
index 03ba77a..f419a77 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
@@ -54,6 +54,8 @@
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.chrome.browser.util.KeyNavigationUtil;
 import org.chromium.components.omnibox.AutocompleteMatch;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.ViewProvider;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.LazyConstructionPropertyMcp;
@@ -95,7 +97,8 @@
             @NonNull ObservableSupplier<Profile> profileObservableSupplier,
             @NonNull Callback<Tab> bringToForegroundCallback,
             @NonNull Supplier<TabWindowManager> tabWindowManagerSupplier,
-            @NonNull BookmarkState bookmarkState, @NonNull ActionChipsDelegate actionChipsDelegate,
+            @NonNull BookmarkState bookmarkState,
+            @NonNull OmniboxActionDelegate omniboxActionDelegate,
             @NonNull OmniboxSuggestionsDropdownScrollListener scrollListener,
             @NonNull OpenHistoryClustersDelegate openHistoryClustersDelegate) {
         mParent = parent;
@@ -114,7 +117,7 @@
                 urlBarEditingTextProvider, listModel, new Handler(), modalDialogManagerSupplier,
                 activityTabSupplier, shareDelegateSupplier, locationBarDataProvider,
                 bringToForegroundCallback, tabWindowManagerSupplier, bookmarkState,
-                actionChipsDelegate, openHistoryClustersDelegate);
+                omniboxActionDelegate, openHistoryClustersDelegate);
         mMediator.initDefaultProcessors();
 
         mScrollListenerList.addObserver(scrollListener);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index 57ca39b..7a493f1 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -30,7 +30,6 @@
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener;
-import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics.RefineActionUsage;
 import org.chromium.chrome.browser.omnibox.suggestions.base.HistoryClustersProcessor.OpenHistoryClustersDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor.BookmarkState;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
@@ -45,7 +44,10 @@
 import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteResult;
+import org.chromium.components.omnibox.OmniboxMetrics;
+import org.chromium.components.omnibox.OmniboxMetrics.RefineActionUsage;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
@@ -154,7 +156,8 @@
             @NonNull LocationBarDataProvider locationBarDataProvider,
             @NonNull Callback<Tab> bringTabToFrontCallback,
             @NonNull Supplier<TabWindowManager> tabWindowManagerSupplier,
-            @NonNull BookmarkState bookmarkState, @NonNull ActionChipsDelegate actionChipsDelegate,
+            @NonNull BookmarkState bookmarkState,
+            @NonNull OmniboxActionDelegate omniboxActionDelegate,
             @NonNull OpenHistoryClustersDelegate openHistoryClustersDelegate) {
         mContext = context;
         mControllerProvider = controllerProvider;
@@ -168,7 +171,7 @@
         mTabWindowManagerSupplier = tabWindowManagerSupplier;
         mSuggestionModels = mListPropertyModel.get(SuggestionListProperties.SUGGESTION_MODELS);
         mDropdownViewInfoListBuilder = new DropdownItemViewInfoListBuilder(activityTabSupplier,
-                bookmarkState, actionChipsDelegate, openHistoryClustersDelegate);
+                bookmarkState, omniboxActionDelegate, openHistoryClustersDelegate);
         mDropdownViewInfoListBuilder.setShareDelegateSupplier(shareDelegateSupplier);
         mDropdownViewInfoListManager =
                 new DropdownItemViewInfoListManager(mSuggestionModels, context);
@@ -327,10 +330,10 @@
         } else {
             stopMeasuringSuggestionRequestToUiModelTime();
             cancelAutocompleteRequests();
-            SuggestionsMetrics.recordOmniboxFocusResultedInNavigation(
+            OmniboxMetrics.recordOmniboxFocusResultedInNavigation(
                     mOmniboxFocusResultedInNavigation);
-            SuggestionsMetrics.recordRefineActionUsage(mRefineActionUsage);
-            SuggestionsMetrics.recordSuggestionsListScrolled(
+            OmniboxMetrics.recordRefineActionUsage(mRefineActionUsage);
+            OmniboxMetrics.recordSuggestionsListScrolled(
                     mDataProvider.getPageClassification(
                             mDelegate.didFocusUrlFromFakebox(), /*isPrefetch=*/false),
                     mSuggestionsListScrolled);
@@ -792,7 +795,7 @@
     private void loadUrlForOmniboxMatch(int matchIndex, @NonNull AutocompleteMatch suggestion,
             @NonNull GURL url, long inputStart, boolean inVisibleSuggestionList) {
         try (TraceEvent e = TraceEvent.scoped("AutocompleteMediator.loadUrlFromOmniboxMatch")) {
-            SuggestionsMetrics.recordFocusToOpenTime(System.currentTimeMillis() - mUrlFocusTime);
+            OmniboxMetrics.recordFocusToOpenTime(System.currentTimeMillis() - mUrlFocusTime);
 
             // Clear the deferred site load action in case it executes. Reclaims a bit of memory.
             mDeferredLoadAction = null;
@@ -988,7 +991,7 @@
      * @param suggestion The suggestion selected.
      */
     private void recordMetrics(int matchIndex, int disposition, AutocompleteMatch suggestion) {
-        SuggestionsMetrics.recordUsedSuggestionFromCache(mAutocompleteResult.isFromCachedResult());
+        OmniboxMetrics.recordUsedSuggestionFromCache(mAutocompleteResult.isFromCachedResult());
 
         // Do not attempt to record other metrics for cached suggestions if the source of the list
         // is local cache. These suggestions do not have corresponding native objects and will fail
@@ -1128,12 +1131,12 @@
 
         if (mFirstSuggestionListModelCreatedTime == null) {
             mFirstSuggestionListModelCreatedTime = SystemClock.uptimeMillis();
-            SuggestionsMetrics.recordSuggestionRequestToModelTime(/*isFirst=*/true,
+            OmniboxMetrics.recordSuggestionRequestToModelTime(/*isFirst=*/true,
                     mFirstSuggestionListModelCreatedTime - mLastSuggestionRequestTime);
         }
 
         if (isFinal) {
-            SuggestionsMetrics.recordSuggestionRequestToModelTime(
+            OmniboxMetrics.recordSuggestionRequestToModelTime(
                     /*isFirst=*/false, SystemClock.uptimeMillis() - mLastSuggestionRequestTime);
             stopMeasuringSuggestionRequestToUiModelTime();
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index 0a42994..7986fde 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -61,7 +61,10 @@
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteMatchBuilder;
 import org.chromium.components.omnibox.AutocompleteResult;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -101,7 +104,7 @@
     private @Mock TabModel mTabModel;
     private @Mock TabWindowManager mTabManager;
     private @Mock WindowAndroid mMockWindowAndroid;
-    private @Mock ActionChipsDelegate mActionChipsDelegate;
+    private @Mock OmniboxActionDelegate mOmniboxActionDelegate;
     private @Mock LargeIconBridge.Natives mLargeIconBridgeJniMock;
     @Mock
     private HistoryClustersProcessor.OpenHistoryClustersDelegate mOpenHistoryClustersDelegate;
@@ -132,7 +135,7 @@
                     mAutocompleteDelegate, mTextStateProvider, mListModel,
                     new Handler(), () -> mModalDialogManager, null, null,
                     mLocationBarDataProvider, tab -> {}, mTabWindowManagerSupplier, url -> false,
-                    mActionChipsDelegate, mOpenHistoryClustersDelegate);
+                    mOmniboxActionDelegate, mOpenHistoryClustersDelegate);
             mMediator.setAutocompleteProfile(mProfile);
         });
         // clang-format on
@@ -705,22 +708,22 @@
             @Nullable Integer lastHistogramTime) {
         Assert.assertEquals(firstHistogramTotalCount,
                 RecordHistogram.getHistogramTotalCountForTesting(
-                        SuggestionsMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST));
+                        OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST));
         Assert.assertEquals(lastHistogramTotalCount,
                 RecordHistogram.getHistogramTotalCountForTesting(
-                        SuggestionsMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST));
+                        OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST));
 
         if (firstHistogramTime != null) {
             Assert.assertEquals(1,
                     RecordHistogram.getHistogramValueCountForTesting(
-                            SuggestionsMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST,
+                            OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST,
                             firstHistogramTime));
         }
 
         if (lastHistogramTime != null) {
             Assert.assertEquals(1,
                     RecordHistogram.getHistogramValueCountForTesting(
-                            SuggestionsMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST,
+                            OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST,
                             lastHistogramTime));
         }
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
index 6347364..3f9e491 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
@@ -39,6 +39,7 @@
 import org.chromium.components.image_fetcher.ImageFetcherFactory;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteResult;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
@@ -53,7 +54,7 @@
 
     private final @NonNull List<SuggestionProcessor> mPriorityOrderedSuggestionProcessors;
     private final @NonNull Supplier<Tab> mActivityTabSupplier;
-    private final @NonNull ActionChipsDelegate mActionChipsDelegate;
+    private final @NonNull OmniboxActionDelegate mOmniboxActionDelegate;
 
     private @Nullable DividerLineProcessor mDividerLineProcessor;
     private @Nullable HeaderProcessor mHeaderProcessor;
@@ -67,13 +68,13 @@
     private OpenHistoryClustersDelegate mOpenHistoryClustersDelegate;
 
     DropdownItemViewInfoListBuilder(@NonNull Supplier<Tab> tabSupplier, BookmarkState bookmarkState,
-            @NonNull ActionChipsDelegate actionChipsDelegate,
+            @NonNull OmniboxActionDelegate omniboxActionDelegate,
             OpenHistoryClustersDelegate openHistoryClustersDelegate) {
         mPriorityOrderedSuggestionProcessors = new ArrayList<>();
         mDropdownHeight = DROPDOWN_HEIGHT_UNKNOWN;
         mActivityTabSupplier = tabSupplier;
         mBookmarkState = bookmarkState;
-        mActionChipsDelegate = actionChipsDelegate;
+        mOmniboxActionDelegate = omniboxActionDelegate;
         mOpenHistoryClustersDelegate = openHistoryClustersDelegate;
     }
 
@@ -106,18 +107,18 @@
         registerSuggestionProcessor(new EditUrlSuggestionProcessor(
                 context, host, delegate, mFaviconFetcher, mActivityTabSupplier, shareSupplier));
         registerSuggestionProcessor(new AnswerSuggestionProcessor(
-                context, host, mActionChipsDelegate, textProvider, imageFetcherSupplier));
+                context, host, mOmniboxActionDelegate, textProvider, imageFetcherSupplier));
         registerSuggestionProcessor(
                 new ClipboardSuggestionProcessor(context, host, mFaviconFetcher));
         registerSuggestionProcessor(new HistoryClustersProcessor(mOpenHistoryClustersDelegate,
                 context, host, textProvider, mFaviconFetcher, mBookmarkState));
         registerSuggestionProcessor(new EntitySuggestionProcessor(
-                context, host, mActionChipsDelegate, imageFetcherSupplier));
+                context, host, mOmniboxActionDelegate, imageFetcherSupplier));
         registerSuggestionProcessor(
-                new TailSuggestionProcessor(context, host, mActionChipsDelegate));
+                new TailSuggestionProcessor(context, host, mOmniboxActionDelegate));
         registerSuggestionProcessor(new MostVisitedTilesProcessor(context, host, mFaviconFetcher));
         registerSuggestionProcessor(new BasicSuggestionProcessor(context, host,
-                mActionChipsDelegate, textProvider, mFaviconFetcher, mBookmarkState));
+                mOmniboxActionDelegate, textProvider, mFaviconFetcher, mBookmarkState));
     }
 
     void destroy() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java
index 8a2ea2f2..745430b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java
@@ -45,6 +45,8 @@
 import org.chromium.components.omnibox.AutocompleteResult;
 import org.chromium.components.omnibox.GroupsProto.GroupsInfo;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.ShadowGURL;
 
@@ -65,7 +67,7 @@
     private @Mock SuggestionProcessor mMockSuggestionProcessor;
     private @Mock HeaderProcessor mMockHeaderProcessor;
     private @Mock DividerLineProcessor mMockDividerLineProcessor;
-    private @Mock ActionChipsDelegate mMockActionChipsDelegate;
+    private @Mock OmniboxActionDelegate mMockActionChipsDelegate;
     @Mock
     private HistoryClustersProcessor.OpenHistoryClustersDelegate mOpenHistoryClustersDelegate;
     DropdownItemViewInfoListBuilder mBuilder;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java
index f30c05e..ab75e82 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.omnibox.GroupsProto.GroupSection;
 import org.chromium.components.omnibox.GroupsProto.GroupsInfo;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java
index e9c4d9b2..71afeca 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java
@@ -36,6 +36,7 @@
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.omnibox.GroupsProto.GroupsInfo;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.ListObservable.ListObserver;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
index ccd17a5..c46e5fe7 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
@@ -35,6 +35,7 @@
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.chrome.browser.util.KeyNavigationUtil;
 import org.chromium.components.browser_ui.styles.ChromeColors;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.ui.base.ViewUtils;
 
 import java.lang.annotation.Retention;
@@ -361,7 +362,7 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         try (TraceEvent tracing = TraceEvent.scoped("OmniboxSuggestionsList.Measure");
-                TimingMetric metric = SuggestionsMetrics.recordSuggestionListMeasureTime()) {
+                TimingMetric metric = OmniboxMetrics.recordSuggestionListMeasureTime()) {
             OmniboxAlignment omniboxAlignment = mEmbedder.getCurrentAlignment();
             maybeUpdateLayoutParams(omniboxAlignment.top);
             boolean useAlignmentSpecifiedHeight = OmniboxFeatures.omniboxConsumesImeInsets();
@@ -457,7 +458,7 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         try (TraceEvent tracing = TraceEvent.scoped("OmniboxSuggestionsList.Layout");
-                TimingMetric metric = SuggestionsMetrics.recordSuggestionListLayoutTime()) {
+                TimingMetric metric = OmniboxMetrics.recordSuggestionListLayoutTime()) {
             super.onLayout(changed, l, t, r, b);
         }
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownAdapter.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownAdapter.java
index 5766f1a..c91c2b4 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownAdapter.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdownAdapter.java
@@ -15,6 +15,7 @@
 
 import org.chromium.base.TraceEvent;
 import org.chromium.base.metrics.TimingMetric;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
 
@@ -39,7 +40,7 @@
 
     /* package */ void recordSessionMetrics() {
         if (mNumSessionViewsBound > 0) {
-            SuggestionsMetrics.recordSuggestionViewReuseStats(mNumSessionViewsCreated,
+            OmniboxMetrics.recordSuggestionViewReuseStats(mNumSessionViewsCreated,
                     100 * (mNumSessionViewsBound - mNumSessionViewsCreated)
                             / mNumSessionViewsBound);
         }
@@ -116,7 +117,7 @@
         // the creation of a view holder.
         try (TraceEvent tracing =
                         TraceEvent.scoped("OmniboxSuggestionsList.CreateView", "type:" + viewType);
-                TimingMetric metric = SuggestionsMetrics.recordSuggestionViewCreateTime()) {
+                TimingMetric metric = OmniboxMetrics.recordSuggestionViewCreateTime()) {
             return super.createView(parent, viewType);
         }
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPool.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPool.java
index 7b292a9..3e2aa9d 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPool.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPool.java
@@ -14,6 +14,8 @@
 
 import org.chromium.base.TraceEvent;
 import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
+import org.chromium.components.omnibox.OmniboxMetrics;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -129,9 +131,9 @@
         stopCreatingViews();
         ViewHolder result = super.getRecycledView(viewType);
         if (result == null) {
-            SuggestionsMetrics.recordSuggestionsViewCreatedType(viewType);
+            OmniboxMetrics.recordSuggestionsViewCreatedType(viewType);
         } else {
-            SuggestionsMetrics.recordSuggestionsViewReusedType(viewType);
+            OmniboxMetrics.recordSuggestionsViewReusedType(viewType);
         }
         return result;
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPoolTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPoolTest.java
index 9342914..ece4c9f2 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPoolTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/PreWarmingRecycledViewPoolTest.java
@@ -38,6 +38,7 @@
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 
 import java.util.Arrays;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/AnswerSuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/AnswerSuggestionProcessor.java
index 6c3727c..3e035083 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/AnswerSuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/AnswerSuggestionProcessor.java
@@ -16,8 +16,6 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionDrawableState;
@@ -26,6 +24,8 @@
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
 import org.chromium.components.omnibox.SuggestionAnswer;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
@@ -50,10 +50,10 @@
      * @param suggestionHost A handle to the object using the suggestions.
      */
     public AnswerSuggestionProcessor(Context context, SuggestionHost suggestionHost,
-            ActionChipsDelegate actionChipsDelegate,
+            OmniboxActionDelegate omniboxActionDelegate,
             UrlBarEditingTextStateProvider editingTextProvider,
             Supplier<ImageFetcher> imageFetcherSupplier) {
-        super(context, suggestionHost, actionChipsDelegate, null);
+        super(context, suggestionHost, omniboxActionDelegate, null);
         mSuggestionHost = suggestionHost;
         mPendingAnswerRequestUrls = new HashMap<>();
         mUrlBarEditingTextProvider = editingTextProvider;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsProcessor.java
index fa6967e6..9b303b5 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsProcessor.java
@@ -12,13 +12,13 @@
 import androidx.collection.ArraySet;
 
 import org.chromium.chrome.browser.omnibox.R;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
-import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics;
 import org.chromium.components.browser_ui.widget.chips.ChipProperties;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.EntityInfoProto;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.components.omnibox.action.OmniboxAction;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.components.omnibox.action.OmniboxActionInSuggest;
 import org.chromium.components.omnibox.action.OmniboxActionType;
 import org.chromium.components.omnibox.action.OmniboxPedal;
@@ -33,7 +33,7 @@
  */
 public class ActionChipsProcessor {
     private final @NonNull Context mContext;
-    private final @NonNull ActionChipsDelegate mActionChipsDelegate;
+    private final @NonNull OmniboxActionDelegate mOmniboxActionDelegate;
     private final @NonNull SuggestionHost mSuggestionHost;
     private final @NonNull Set<Integer> mLastVisiblePedals = new ArraySet<>();
     private final @NonNull SparseBooleanArray mActionInSuggestShownOrUsed =
@@ -44,13 +44,13 @@
     /**
      * @param context An Android context.
      * @param suggestionHost Component receiving suggestion events.
-     * @param actionChipsDelegate A delegate that will responsible for pedals.
+     * @param omniboxActionDelegate A delegate that will responsible for pedals.
      */
     public ActionChipsProcessor(@NonNull Context context, @NonNull SuggestionHost suggestionHost,
-            @NonNull ActionChipsDelegate actionChipsDelegate) {
+            @NonNull OmniboxActionDelegate omniboxActionDelegate) {
         mContext = context;
         mSuggestionHost = suggestionHost;
-        mActionChipsDelegate = actionChipsDelegate;
+        mOmniboxActionDelegate = omniboxActionDelegate;
 
         // TODO(crbug/1418077): Migrate this to OmniboxActionInSuggest along with execute logic.
         var pm = mContext.getPackageManager();
@@ -163,7 +163,7 @@
     private void executeAction(@NonNull OmniboxAction action, int position) {
         switch (action.actionId) {
             case OmniboxActionType.HISTORY_CLUSTERS:
-                SuggestionsMetrics.recordResumeJourneyClick(position);
+                OmniboxMetrics.recordResumeJourneyClick(position);
                 break;
 
             case OmniboxActionType.ACTION_IN_SUGGEST:
@@ -173,7 +173,7 @@
                 break;
         }
         mSuggestionHost.finishInteraction();
-        mActionChipsDelegate.execute(action);
+        mOmniboxActionDelegate.execute(action);
     }
 
     /**
@@ -183,19 +183,19 @@
      */
     private void recordActionsShown() {
         for (Integer pedal : mLastVisiblePedals) {
-            SuggestionsMetrics.recordPedalShown(pedal);
+            OmniboxMetrics.recordPedalShown(pedal);
         }
 
         for (var actionIndex = 0; actionIndex < mActionInSuggestShownOrUsed.size(); actionIndex++) {
             int actionType = mActionInSuggestShownOrUsed.keyAt(actionIndex);
             boolean wasUsed = mActionInSuggestShownOrUsed.valueAt(actionIndex);
-            SuggestionsMetrics.recordActionInSuggestShown(actionType);
+            OmniboxMetrics.recordActionInSuggestShown(actionType);
             if (wasUsed) {
-                SuggestionsMetrics.recordActionInSuggestUsed(actionType);
+                OmniboxMetrics.recordActionInSuggestUsed(actionType);
             }
         }
 
-        SuggestionsMetrics.recordResumeJourneyShown(mJourneysActionShownPosition);
+        OmniboxMetrics.recordResumeJourneyShown(mJourneysActionShownPosition);
 
         mJourneysActionShownPosition = -1;
         mLastVisiblePedals.clear();
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionProcessorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionProcessorUnitTest.java
index 618ccb06..96660b44 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionProcessorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionProcessorUnitTest.java
@@ -30,11 +30,11 @@
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher.FaviconFetchCompleteListener;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher.FaviconType;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteMatchBuilder;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 import org.chromium.url.JUnitTestGURLs;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java
index 95ed08d0..81f45dbc 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java
@@ -343,7 +343,7 @@
         ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
         if (layoutParams == null) {
             layoutParams =
-                    new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+                    new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
         }
 
         if (layoutParams instanceof MarginLayoutParams) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java
index e01f99b1..5b5d242 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java
@@ -21,6 +21,7 @@
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.ImageView;
 
+import androidx.appcompat.app.ActionBar.LayoutParams;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import org.junit.Assert;
@@ -278,6 +279,7 @@
         Assert.assertEquals(13, layoutParams.topMargin);
         Assert.assertEquals(sideSpacing, layoutParams.rightMargin);
         Assert.assertEquals(17, layoutParams.bottomMargin);
+        Assert.assertEquals(LayoutParams.MATCH_PARENT, layoutParams.width);
     }
 
     @Test
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
index 89fd6ff..3176a68 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
@@ -17,13 +17,13 @@
 import org.chromium.chrome.browser.omnibox.MatchClassificationStyle;
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProperties.Action;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteMatch.MatchClassification;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
@@ -48,7 +48,7 @@
      * @param faviconFetcher A mechanism to use to retrieve favicons.
      */
     public BaseSuggestionViewProcessor(@NonNull Context context, @NonNull SuggestionHost host,
-            @Nullable ActionChipsDelegate actionChipsDelegate,
+            @Nullable OmniboxActionDelegate omniboxActionDelegate,
             @Nullable FaviconFetcher faviconFetcher) {
         mContext = context;
         mSuggestionHost = host;
@@ -60,8 +60,8 @@
                 R.dimen.omnibox_suggestion_content_height);
         mFaviconFetcher = faviconFetcher;
 
-        if (actionChipsDelegate != null) {
-            mActionChipsProcessor = new ActionChipsProcessor(context, host, actionChipsDelegate);
+        if (omniboxActionDelegate != null) {
+            mActionChipsProcessor = new ActionChipsProcessor(context, host, omniboxActionDelegate);
         } else {
             mActionChipsProcessor = null;
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/HistoryClustersProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/HistoryClustersProcessor.java
index 82ccec7..4c592ab 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/HistoryClustersProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/HistoryClustersProcessor.java
@@ -12,15 +12,15 @@
 
 import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
-import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties;
 import org.chromium.components.omnibox.AutocompleteMatch;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.components.omnibox.action.HistoryClustersAction;
 import org.chromium.components.omnibox.action.OmniboxAction;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
 import org.chromium.components.omnibox.action.OmniboxActionType;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -42,7 +42,7 @@
 
     /**
      * See {@link BasicSuggestionProcessor#BasicSuggestionProcessor(Context, SuggestionHost,
-     * ActionChipsDelegate, UrlBarEditingTextStateProvider, FaviconFetcher, BookmarkState)}
+     * OmniboxActionDelegate, UrlBarEditingTextStateProvider, FaviconFetcher, BookmarkState)}
      */
     public HistoryClustersProcessor(OpenHistoryClustersDelegate openHistoryClustersDelegate,
             @NonNull Context context, @NonNull SuggestionHost suggestionHost,
@@ -57,7 +57,7 @@
     public void onUrlFocusChange(boolean hasFocus) {
         super.onUrlFocusChange(hasFocus);
         if (!hasFocus) {
-            SuggestionsMetrics.recordResumeJourneyShown(mJourneysActionShownPosition);
+            OmniboxMetrics.recordResumeJourneyShown(mJourneysActionShownPosition);
         }
     }
 
@@ -97,7 +97,7 @@
     private void onJourneysSuggestionClicked(HistoryClustersAction action, int position) {
         if (mOpenHistoryClustersDelegate != null) {
             String query = action.query;
-            SuggestionsMetrics.recordResumeJourneyClick(position);
+            OmniboxMetrics.recordResumeJourneyClick(position);
             mOpenHistoryClustersDelegate.openHistoryClustersUi(query);
         }
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java
index ee06359..69a9e454 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java
@@ -15,15 +15,15 @@
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.UrlBarData;
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionDrawableState;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionSpannable;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
@@ -46,7 +46,7 @@
     /**
      * @param context An Android context.
      * @param suggestionHost A handle to the object using the suggestions.
-     * @param actionChipsDelegate Delegate that gives us information what action chips should look
+     * @param omniboxActionDelegate Delegate that gives us information what action chips should look
      *         like and how to execute them.
      * @param editingTextProvider A means of accessing the text in the omnibox.
      * @param faviconFetcher Fetcher for favicon images.
@@ -54,10 +54,10 @@
      */
     public BasicSuggestionProcessor(@NonNull Context context,
             @NonNull SuggestionHost suggestionHost,
-            @Nullable ActionChipsDelegate actionChipsDelegate,
+            @Nullable OmniboxActionDelegate omniboxActionDelegate,
             @NonNull UrlBarEditingTextStateProvider editingTextProvider,
             @NonNull FaviconFetcher faviconFetcher, @NonNull BookmarkState bookmarkState) {
-        super(context, suggestionHost, actionChipsDelegate, faviconFetcher);
+        super(context, suggestionHost, omniboxActionDelegate, faviconFetcher);
 
         mUrlBarEditingTextProvider = editingTextProvider;
         mBookmarkState = bookmarkState;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessor.java
index 167fbbf..d03febb0 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessor.java
@@ -16,7 +16,6 @@
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProperties.Action;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.Arrays;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessor.java
index 0407f916..bd3a04aa 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessor.java
@@ -8,8 +8,8 @@
 
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.suggestions.DropdownItemProcessor;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /** A class that handles model and view creation for the suggestion divider line. */
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessorUnitTest.java
index f880f7b..4a3cd55 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/dividerline/DividerLineProcessorUnitTest.java
@@ -14,8 +14,8 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Batch;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.test.R;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 
 /**
  * Tests for {@link DividerLineProcessor}.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
index f994c98..17ff4b5 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
@@ -12,7 +12,6 @@
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.UrlBarDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
@@ -26,6 +25,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.components.ukm.UkmRecorder;
 import org.chromium.ui.base.Clipboard;
 import org.chromium.ui.modelutil.PropertyModel;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
index e27dfa5..d02b74e 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
@@ -17,8 +17,6 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.omnibox.R;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionDrawableState;
@@ -26,6 +24,8 @@
 import org.chromium.components.image_fetcher.ImageFetcher;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
@@ -54,8 +54,9 @@
      * @param suggestionHost A handle to the object using the suggestions.
      */
     public EntitySuggestionProcessor(Context context, SuggestionHost suggestionHost,
-            ActionChipsDelegate actionChipsDelegate, Supplier<ImageFetcher> imageFetcherSupplier) {
-        super(context, suggestionHost, actionChipsDelegate, null);
+            OmniboxActionDelegate omniboxActionDelegate,
+            Supplier<ImageFetcher> imageFetcherSupplier) {
+        super(context, suggestionHost, omniboxActionDelegate, null);
         mSuggestionHost = suggestionHost;
         mPendingImageRequests = new HashMap<>();
         mImageFetcherSupplier = imageFetcherSupplier;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderProcessor.java
index 05723fc..fb647e2 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderProcessor.java
@@ -9,7 +9,7 @@
 import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.suggestions.DropdownItemProcessor;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /** A class that handles model and view creation for the suggestion headers. */
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
index 2ced667..adc743f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
@@ -17,10 +17,8 @@
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.FaviconFetcher;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
-import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionItemViewBuilder;
 import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionViewProperties;
@@ -29,7 +27,9 @@
 import org.chromium.components.browser_ui.widget.tile.TileViewProperties;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteMatch.SuggestTile;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
@@ -131,7 +131,7 @@
             tileModel.set(TileViewProperties.ON_FOCUS_VIA_SELECTION,
                     () -> mSuggestionHost.setOmniboxEditingText(url.getSpec()));
             tileModel.set(TileViewProperties.ON_CLICK, v -> {
-                SuggestionsMetrics.recordSuggestTileTypeUsed(itemIndex, isSearch);
+                OmniboxMetrics.recordSuggestTileTypeUsed(itemIndex, isSearch);
                 mSuggestionHost.onSuggestionClicked(suggestion, matchIndex, url);
             });
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java
index 26d8d4f..1640a23 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java
@@ -7,14 +7,14 @@
 import android.content.Context;
 
 import org.chromium.chrome.browser.omnibox.R;
-import org.chromium.chrome.browser.omnibox.suggestions.ActionChipsDelegate;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionDrawableState;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionSpannable;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.action.OmniboxActionDelegate;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -28,8 +28,8 @@
      * @param suggestionHost A handle to the object using the suggestions.
      */
     public TailSuggestionProcessor(Context context, SuggestionHost suggestionHost,
-            ActionChipsDelegate actionChipsDelegate) {
-        super(context, suggestionHost, actionChipsDelegate, null);
+            OmniboxActionDelegate omniboxActionDelegate) {
+        super(context, suggestionHost, omniboxActionDelegate, null);
         mAlignTailSuggestions = DeviceFormFactor.isNonMultiDisplayContextOnTablet(context);
     }
 
diff --git a/chrome/browser/ui/android/passwords/manual_filling_view_android.cc b/chrome/browser/ui/android/passwords/manual_filling_view_android.cc
index 00da835..1d12f5d02 100644
--- a/chrome/browser/ui/android/passwords/manual_filling_view_android.cc
+++ b/chrome/browser/ui/android/passwords/manual_filling_view_android.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/password_manager/android/password_accessory_metrics_util.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "components/autofill/core/browser/ui/accessory_sheet_data.h"
+#include "components/autofill/core/browser/ui/accessory_sheet_enums.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/password_manager/core/browser/credential_cache.h"
 #include "components/password_manager/core/browser/password_form.h"
@@ -209,14 +210,16 @@
         base::android::AttachCurrentThread(), obj, static_cast<int>(tab_type));
   }
 }
-
-void ManualFillingViewAndroid::OnAutomaticGenerationStatusChanged(
-    bool available) {
-  if (!available && java_object_internal_.is_null())
+void ManualFillingViewAndroid::OnAccessoryActionAvailabilityChanged(
+    ShouldShowAction shouldShowAction,
+    autofill::AccessoryAction action) {
+  if (!shouldShowAction && java_object_internal_.is_null()) {
     return;
+  }
   if (auto obj = GetOrCreateJavaObject()) {
-    Java_ManualFillingComponentBridge_onAutomaticGenerationStatusChanged(
-        base::android::AttachCurrentThread(), obj, available);
+    Java_ManualFillingComponentBridge_onAccessoryActionAvailabilityChanged(
+        base::android::AttachCurrentThread(), obj, shouldShowAction.value(),
+        static_cast<int>(action));
   }
 }
 
@@ -338,7 +341,9 @@
   // Bypass the generation controller when sending this status to the UI to
   // avoid setup overhead, since its logic is currently not needed for tests.
   ManualFillingControllerImpl::GetOrCreate(web_contents)
-      ->OnAutomaticGenerationStatusChanged(j_available);
+      ->OnAccessoryActionAvailabilityChanged(
+          ManualFillingController::ShouldShowAction(j_available),
+          autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC);
 }
 
 // static
diff --git a/chrome/browser/ui/android/passwords/manual_filling_view_android.h b/chrome/browser/ui/android/passwords/manual_filling_view_android.h
index 7fb6616..998959fa 100644
--- a/chrome/browser/ui/android/passwords/manual_filling_view_android.h
+++ b/chrome/browser/ui/android/passwords/manual_filling_view_android.h
@@ -41,7 +41,9 @@
 
   // ManualFillingViewInterface:
   void OnItemsAvailable(autofill::AccessorySheetData data) override;
-  void OnAutomaticGenerationStatusChanged(bool available) override;
+  void OnAccessoryActionAvailabilityChanged(
+      ShouldShowAction shouldShowAction,
+      autofill::AccessoryAction action) override;
   void CloseAccessorySheet() override;
   void SwapSheetWithKeyboard() override;
   void Show(WaitForKeyboard wait_for_keyboard) override;
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 51f07b0..afd2d18 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3695,6 +3695,18 @@
       <message name="IDS_NO_BOOKMARKS" desc="Text describing that there is no bookmarks in a bookmark folder.">
         No bookmarks
       </message>
+      <message name="IDS_BOOKMARK_MANAGER_EMPTY_STATE" desc="Text appearing on an empty tab that indicates that bookmarked page appear on this tab.">
+        You’ll find your bookmarks here
+      </message>
+      <message name="IDS_BOOKMARK_MANAGER_BACK_TO_PAGE_BY_ADDING_BOOKMARK" desc="Text appearing on an empty tab that indicates that users can get back to pages in this tab by adding bookmark.">
+        You can get back to a page that’s important to you by adding a bookmark
+      </message>
+      <message name="IDS_READING_LIST_MANAGER_EMPTY_STATE" desc="Text appearing on an empty tab that indicates that reading list page appear on this tab.">
+        You’ll find your reading list here
+      </message>
+      <message name="IDS_READING_LIST_MANAGER_SAVE_PAGE_TO_READ_LATER" desc="Text appearing on an empty tab that indicates that users can save pages read later in this tab.">
+        You can save pages to read later or offline
+      </message>
       <message name="IDS_BOOKMARKS_COUNT" desc="Text describing the number of bookmarks in a bookmark folder [ICU Syntax]">
         {BOOKMARKS_COUNT, plural,
           =1 {<ph name="BOOKMARKS_COUNT_ONE">%1$d<ex>1</ex></ph> bookmark}
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_BOOKMARK_MANAGER_BACK_TO_PAGE_BY_ADDING_BOOKMARK.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_BOOKMARK_MANAGER_BACK_TO_PAGE_BY_ADDING_BOOKMARK.png.sha1
new file mode 100644
index 0000000..c559969
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_BOOKMARK_MANAGER_BACK_TO_PAGE_BY_ADDING_BOOKMARK.png.sha1
@@ -0,0 +1 @@
+63d1d5cde6d0cf280c85c2f7a1f2af349804cde2
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_BOOKMARK_MANAGER_EMPTY_STATE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_BOOKMARK_MANAGER_EMPTY_STATE.png.sha1
new file mode 100644
index 0000000..c559969
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_BOOKMARK_MANAGER_EMPTY_STATE.png.sha1
@@ -0,0 +1 @@
+63d1d5cde6d0cf280c85c2f7a1f2af349804cde2
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_MANAGER_EMPTY_STATE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_MANAGER_EMPTY_STATE.png.sha1
new file mode 100644
index 0000000..7d083de
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_MANAGER_EMPTY_STATE.png.sha1
@@ -0,0 +1 @@
+f56689bfdc35e1513f4f1ec265cafefe9e2e2ec6
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_MANAGER_SAVE_PAGE_TO_READ_LATER.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_MANAGER_SAVE_PAGE_TO_READ_LATER.png.sha1
new file mode 100644
index 0000000..7d083de
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_MANAGER_SAVE_PAGE_TO_READ_LATER.png.sha1
@@ -0,0 +1 @@
+f56689bfdc35e1513f4f1ec265cafefe9e2e2ec6
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
index 579e8fe..c896306 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
@@ -35,6 +35,7 @@
 import org.chromium.chrome.browser.omnibox.LocationBar;
 import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
 import org.chromium.chrome.browser.omnibox.NewTabPageDelegate;
+import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
 import org.chromium.chrome.browser.omnibox.UrlBarData;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
@@ -127,6 +128,11 @@
                 getResources().getDimensionPixelOffset(R.dimen.toolbar_edge_padding);
     }
 
+    public boolean isToolbarButtonReorderingEnabled() {
+        return ChromeFeatureList.sTabStripRedesign.isEnabled()
+            && !OmniboxFeatures.isTabStripToolbarReorderingDisabled();
+    }
+
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
@@ -135,8 +141,9 @@
         mForwardButton = findViewById(R.id.forward_button);
         mReloadButton = findViewById(R.id.refresh_button);
 
-        // Reposition home button to align with desktop ordering when TSR enabled.
-        if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+        // Reposition home button to align with desktop ordering when TSR enabled and toolbar
+        // reordering not disabled
+        if (isToolbarButtonReorderingEnabled()) {
             // Remove home button view added in XML and adding back with different ordering
             // programmatically.
             ((ViewGroup) mHomeButton.getParent()).removeView(mHomeButton);
@@ -190,7 +197,7 @@
         mHomeButton.setOnKeyListener(new KeyboardNavigationListener() {
             @Override
             public View getNextFocusForward() {
-                if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+                if (isToolbarButtonReorderingEnabled()) {
                     return findViewById(R.id.url_bar);
                 } else {
                     if (mBackButton.isFocusable()) {
@@ -205,7 +212,7 @@
 
             @Override
             public View getNextFocusBackward() {
-                if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+                if (isToolbarButtonReorderingEnabled()) {
                     return findViewById(R.id.refresh_button);
                 } else {
                     return findViewById(R.id.menu_button);
@@ -227,7 +234,7 @@
 
             @Override
             public View getNextFocusBackward() {
-                if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+                if (isToolbarButtonReorderingEnabled()) {
                     return findViewById(R.id.menu_button);
                 } else {
                     if (mHomeButton.getVisibility() == VISIBLE) {
@@ -251,7 +258,7 @@
             public View getNextFocusBackward() {
                 if (mBackButton.isFocusable()) {
                     return mBackButton;
-                } else if (!ChromeFeatureList.sTabStripRedesign.isEnabled()
+                } else if (!isToolbarButtonReorderingEnabled()
                         && mHomeButton.getVisibility() == VISIBLE) {
                     return findViewById(R.id.home_button);
                 } else {
@@ -265,7 +272,7 @@
         mReloadButton.setOnKeyListener(new KeyboardNavigationListener() {
             @Override
             public View getNextFocusForward() {
-                if (ChromeFeatureList.sTabStripRedesign.isEnabled()
+                if (isToolbarButtonReorderingEnabled()
                         && mHomeButton.getVisibility() == VISIBLE) {
                     return findViewById(R.id.home_button);
                 } else {
@@ -279,7 +286,7 @@
                     return mForwardButton;
                 } else if (mBackButton.isFocusable()) {
                     return mBackButton;
-                } else if (!ChromeFeatureList.sTabStripRedesign.isEnabled()
+                } else if (!isToolbarButtonReorderingEnabled()
                         && mHomeButton.getVisibility() == VISIBLE) {
                     return findViewById(R.id.home_button);
                 } else {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
index cc755a7..d29563f8 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
@@ -36,6 +36,7 @@
 import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
 import org.chromium.chrome.browser.omnibox.LocationBarCoordinatorTablet;
 import org.chromium.chrome.browser.omnibox.LocationBarLayout;
+import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
 import org.chromium.chrome.browser.toolbar.HomeButton;
 import org.chromium.chrome.browser.toolbar.R;
@@ -118,7 +119,7 @@
 
     @Test
     @EnableFeatures(ChromeFeatureList.TAB_STRIP_REDESIGN)
-    public void testButtonPositionForTSR() {
+    public void testButtonPosition_TSR() {
         mToolbarTablet.onFinishInflate();
         assertEquals("Back button position is not as expected for Tab Strip Redesign", mBackButton,
                 mToolbarTabletLayout.getChildAt(0));
@@ -131,6 +132,29 @@
     }
 
     @Test
+    @EnableFeatures(ChromeFeatureList.TAB_STRIP_REDESIGN)
+    public void testButtonPosition_TSR_DisableToolbarReordering() {
+        OmniboxFeatures.TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING.setForTesting(true);
+
+        // Resetup for feature param to enable before Native.
+        setUp();
+        mToolbarTablet.onFinishInflate();
+
+        assertEquals("Home button position is not as expected for TSR disable Toolbar reordering",
+                mHomeButton, mToolbarTabletLayout.getChildAt(0));
+        assertEquals("Back button position is not as expected for TSR disable Toolbar reordering",
+                mBackButton, mToolbarTabletLayout.getChildAt(1));
+        assertEquals(
+                "Forward button position is not as expected for TSR disable Toolbar reordering",
+                mForwardButton, mToolbarTabletLayout.getChildAt(2));
+        assertEquals(
+                "Reloading button position is not as expected for TSR disable Toolbar reordering",
+                mReloadingButton, mToolbarTabletLayout.getChildAt(3));
+
+        OmniboxFeatures.TAB_STRIP_REDESIGN_DISABLE_TOOLBAR_REORDERING.setForTesting(false);
+    }
+
+    @Test
     public void onMeasureShortWidth_hidesToolbarButtons() {
         mToolbarTablet.measure(300, 300);
 
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.cc b/chrome/browser/ui/android/webid/account_selection_view_android.cc
index 9e80cab..b5b43d6 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.cc
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.cc
@@ -181,8 +181,7 @@
     const std::string& top_frame_for_display,
     const absl::optional<std::string>& iframe_for_display,
     const std::string& idp_for_display,
-    const content::IdentityProviderMetadata& idp_metadata,
-    IdentityRegistryCallback identity_registry_callback) {
+    const content::IdentityProviderMetadata& idp_metadata) {
   // TODO(crbug.com/1357790): add support on Android.
 }
 
@@ -204,8 +203,10 @@
   return ConvertJavaStringToUTF8(subtitle);
 }
 
-void AccountSelectionViewAndroid::ShowModalDialog(const GURL& url) {
+content::WebContents* AccountSelectionViewAndroid::ShowModalDialog(
+    const GURL& url) {
   // TODO(crbug.com/1429083): Support the AuthZ modal dialog on Android.
+  return nullptr;
 }
 
 void AccountSelectionViewAndroid::CloseModalDialog() {
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.h b/chrome/browser/ui/android/webid/account_selection_view_android.h
index 34ad8f0..2239def 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.h
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.h
@@ -9,6 +9,7 @@
 
 #include "base/functional/callback.h"
 #include "chrome/browser/ui/webid/account_selection_view.h"
+#include "content/public/browser/web_contents.h"
 
 // This class provides an implementation of the AccountSelectionView interface
 // and communicates via JNI with its AccountSelectionBridge Java counterpart.
@@ -29,11 +30,10 @@
       const std::string& top_frame_for_display,
       const absl::optional<std::string>& iframe_for_display,
       const std::string& idp_for_display,
-      const content::IdentityProviderMetadata& idp_metadata,
-      IdentityRegistryCallback identity_registry_callback) override;
+      const content::IdentityProviderMetadata& idp_metadata) override;
   std::string GetTitle() const override;
   absl::optional<std::string> GetSubtitle() const override;
-  void ShowModalDialog(const GURL& url) override;
+  content::WebContents* ShowModalDialog(const GURL& url) override;
   void CloseModalDialog() override;
 
   void OnAccountSelected(
diff --git a/chrome/browser/ui/ash/ambient/ambient_client_impl.cc b/chrome/browser/ui/ash/ambient/ambient_client_impl.cc
index 4ac1470..3da9a41 100644
--- a/chrome/browser/ui/ash/ambient/ambient_client_impl.cc
+++ b/chrome/browser/ui/ash/ambient/ambient_client_impl.cc
@@ -10,12 +10,14 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
 #include "ash/public/cpp/image_downloader.h"
+#include "base/check.h"
 #include "base/functional/callback.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/common/channel_info.h"
+#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/base/consent_level.h"
@@ -28,6 +30,7 @@
 #include "components/user_manager/user_manager.h"
 #include "components/version_info/channel.h"
 #include "content/public/browser/device_service.h"
+#include "content/public/browser/storage_partition.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/http/http_request_headers.h"
@@ -202,6 +205,16 @@
   return profile->GetURLLoaderFactory();
 }
 
+scoped_refptr<network::SharedURLLoaderFactory>
+AmbientClientImpl::GetSigninURLLoaderFactory() {
+  content::BrowserContext* browser_context =
+      ash::BrowserContextHelper::Get()->GetSigninBrowserContext();
+  CHECK(browser_context);
+  Profile* profile = Profile::FromBrowserContext(browser_context);
+  CHECK(profile);
+  return profile->GetURLLoaderFactory();
+}
+
 void AmbientClientImpl::RequestWakeLockProvider(
     mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) {
   content::GetDeviceService().BindWakeLockProvider(std::move(receiver));
diff --git a/chrome/browser/ui/ash/ambient/ambient_client_impl.h b/chrome/browser/ui/ash/ambient/ambient_client_impl.h
index bea6f75..d347f765 100644
--- a/chrome/browser/ui/ash/ambient/ambient_client_impl.h
+++ b/chrome/browser/ui/ash/ambient/ambient_client_impl.h
@@ -33,6 +33,9 @@
   void DownloadImage(const std::string& url,
                      ash::ImageDownloader::DownloadCallback callback) override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
+  scoped_refptr<network::SharedURLLoaderFactory> GetSigninURLLoaderFactory()
+      override;
+
   void RequestWakeLockProvider(
       mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) override;
   bool ShouldUseProdServer() override;
diff --git a/chrome/browser/ui/ash/cast_config_controller_media_router.cc b/chrome/browser/ui/ash/cast_config_controller_media_router.cc
index 187a497..f42f35a 100644
--- a/chrome/browser/ui/ash/cast_config_controller_media_router.cc
+++ b/chrome/browser/ui/ash/cast_config_controller_media_router.cc
@@ -32,7 +32,7 @@
 
 namespace {
 
-absl::optional<media_router::MediaRouter*> media_router_for_test_;
+absl::optional<media_router::MediaRouter*> g_media_router_for_test;
 
 Profile* GetProfile() {
   if (!user_manager::UserManager::IsInitialized())
@@ -48,8 +48,9 @@
 // Returns the MediaRouter instance for the current primary profile, if there is
 // one.
 media_router::MediaRouter* GetMediaRouter() {
-  if (media_router_for_test_)
-    return *media_router_for_test_;
+  if (g_media_router_for_test) {
+    return *g_media_router_for_test;
+  }
 
   Profile* profile = GetProfile();
   if (!profile || !media_router::MediaRouterEnabled(profile))
@@ -154,7 +155,7 @@
 // static
 void CastConfigControllerMediaRouter::SetMediaRouterForTest(
     media_router::MediaRouter* media_router) {
-  media_router_for_test_ = media_router;
+  g_media_router_for_test = media_router;
 }
 
 CastDeviceCache* CastConfigControllerMediaRouter::device_cache() {
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index f1f5dae..e280113 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -149,7 +149,8 @@
 }
 
 void ChromeBrowserMainExtraPartsAsh::PreProfileInit() {
-  if (base::FeatureList::IsEnabled(arc::kEnableArcIdleManager)) {
+  if (base::FeatureList::IsEnabled(arc::kEnableArcIdleManager) ||
+      base::FeatureList::IsEnabled(arc::kVmmSwapPolicy)) {
     // Early init so that later objects can rely on this one.
     arc_window_watcher_ = std::make_unique<ash::ArcWindowWatcher>();
   }
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.cc b/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.cc
new file mode 100644
index 0000000..eabf374
--- /dev/null
+++ b/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.cc
@@ -0,0 +1,16 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.h"
+
+namespace ash {
+
+GlanceablesClassroomClientImpl::GlanceablesClassroomClientImpl(
+    const GlanceablesClassroomClientImpl::CreateRequestSenderCallback&
+        create_request_sender_callback)
+    : create_request_sender_callback_(create_request_sender_callback) {}
+
+GlanceablesClassroomClientImpl::~GlanceablesClassroomClientImpl() = default;
+
+}  // namespace ash
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.h b/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.h
new file mode 100644
index 0000000..663c053
--- /dev/null
+++ b/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.h
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_GLANCEABLES_GLANCEABLES_CLASSROOM_CLIENT_IMPL_H_
+#define CHROME_BROWSER_UI_ASH_GLANCEABLES_GLANCEABLES_CLASSROOM_CLIENT_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ash/glanceables/classroom/glanceables_classroom_client.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "google_apis/common/request_sender.h"
+
+namespace net {
+struct NetworkTrafficAnnotationTag;
+}  // namespace net
+
+namespace ash {
+
+// Provides implementation for `GlanceablesClassroomClient`. Responsible for
+// communication with Google Classroom API.
+class GlanceablesClassroomClientImpl : public GlanceablesClassroomClient {
+ public:
+  // Provides an instance of `google_apis::RequestSender` for the client.
+  using CreateRequestSenderCallback =
+      base::RepeatingCallback<std::unique_ptr<google_apis::RequestSender>(
+          const std::vector<std::string>& scopes,
+          const net::NetworkTrafficAnnotationTag& traffic_annotation_tag)>;
+
+  explicit GlanceablesClassroomClientImpl(
+      const CreateRequestSenderCallback& create_request_sender_callback);
+  GlanceablesClassroomClientImpl(const GlanceablesClassroomClientImpl&) =
+      delete;
+  GlanceablesClassroomClientImpl& operator=(
+      const GlanceablesClassroomClientImpl&) = delete;
+  ~GlanceablesClassroomClientImpl();
+
+ private:
+  // Callback passed from `GlanceablesKeyedService` that creates
+  // `request_sender_`.
+  const CreateRequestSenderCallback create_request_sender_callback_;
+
+  base::WeakPtrFactory<GlanceablesClassroomClientImpl> weak_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_ASH_GLANCEABLES_GLANCEABLES_CLASSROOM_CLIENT_IMPL_H_
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl_unittest.cc b/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl_unittest.cc
new file mode 100644
index 0000000..84b1275
--- /dev/null
+++ b/chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "content/public/test/browser_task_environment.h"
+#include "google_apis/common/dummy_auth_service.h"
+#include "google_apis/common/request_sender.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/test/test_shared_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+class GlanceablesClassroomClientImplTest : public testing::Test {
+ public:
+  void SetUp() override {
+    auto create_request_sender_callback = base::BindLambdaForTesting(
+        [&](const std::vector<std::string>& scopes,
+            const net::NetworkTrafficAnnotationTag& traffic_annotation_tag) {
+          return std::make_unique<google_apis::RequestSender>(
+              std::make_unique<google_apis::DummyAuthService>(),
+              url_loader_factory_, task_environment_.GetMainThreadTaskRunner(),
+              "test-user-agent", TRAFFIC_ANNOTATION_FOR_TESTS);
+        });
+    client_ = std::make_unique<GlanceablesClassroomClientImpl>(
+        create_request_sender_callback);
+  }
+
+  GlanceablesClassroomClientImpl* client() { return client_.get(); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::IO};
+  scoped_refptr<network::TestSharedURLLoaderFactory> url_loader_factory_ =
+      base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
+          /*network_service=*/nullptr,
+          /*is_trusted=*/true);
+  std::unique_ptr<GlanceablesClassroomClientImpl> client_;
+};
+
+TEST_F(GlanceablesClassroomClientImplTest, CreatesClientInstance) {
+  ASSERT_TRUE(client());
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.cc b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.cc
index e29009a..a57581f1 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.cc
@@ -17,6 +17,7 @@
 #include "base/task/thread_pool.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/ash/glanceables/glanceables_classroom_client_impl.h"
 #include "chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.h"
 #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
 #include "components/account_id/account_id.h"
@@ -41,6 +42,7 @@
 GlanceablesKeyedService::~GlanceablesKeyedService() = default;
 
 void GlanceablesKeyedService::Shutdown() {
+  classroom_client_.reset();
   tasks_client_.reset();
   UpdateRegistrationInAsh();
 }
@@ -67,10 +69,13 @@
 }
 
 void GlanceablesKeyedService::CreateClients() {
-  tasks_client_ =
-      std::make_unique<GlanceablesTasksClientImpl>(base::BindRepeating(
-          &GlanceablesKeyedService::CreateRequestSenderForClient,
-          base::Unretained(this)));
+  const auto create_request_sender_callback = base::BindRepeating(
+      &GlanceablesKeyedService::CreateRequestSenderForClient,
+      base::Unretained(this));
+  classroom_client_ = std::make_unique<GlanceablesClassroomClientImpl>(
+      create_request_sender_callback);
+  tasks_client_ = std::make_unique<GlanceablesTasksClientImpl>(
+      create_request_sender_callback);
   UpdateRegistrationInAsh();
 }
 
@@ -81,6 +86,7 @@
   DCHECK(Shell::Get()->glanceables_v2_controller());
   Shell::Get()->glanceables_v2_controller()->UpdateClientsRegistration(
       account_id_, GlanceablesV2Controller::ClientsRegistration{
+                       .classroom_client = classroom_client_.get(),
                        .tasks_client = tasks_client_.get()});
 }
 
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.h b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.h
index 4cd89d9..25e3074 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.h
+++ b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service.h
@@ -29,6 +29,7 @@
 
 namespace ash {
 
+class GlanceablesClassroomClientImpl;
 class GlanceablesTasksClientImpl;
 
 // Browser context keyed service that owns implementations of interfaces from
@@ -75,6 +76,9 @@
   // Account id associated with the primary profile.
   const AccountId account_id_;
 
+  // Instance of the `GlanceablesClassroomClient` interface implementation.
+  std::unique_ptr<GlanceablesClassroomClientImpl> classroom_client_;
+
   // Instance of the `GlanceablesTasksClient` interface implementation.
   std::unique_ptr<GlanceablesTasksClientImpl> tasks_client_;
 };
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc
index 478e979..f7bb5f2 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc
@@ -59,12 +59,15 @@
 
 TEST_F(GlanceablesKeyedServiceTest, RegistersClientsInAsh) {
   auto* const controller = Shell::Get()->glanceables_v2_controller();
+  EXPECT_FALSE(controller->GetClassroomClient());
   EXPECT_FALSE(controller->GetTasksClient());
 
   auto service = std::make_unique<GlanceablesKeyedService>(profile());
+  EXPECT_TRUE(controller->GetClassroomClient());
   EXPECT_TRUE(controller->GetTasksClient());
 
   service->Shutdown();
+  EXPECT_FALSE(controller->GetClassroomClient());
   EXPECT_FALSE(controller->GetTasksClient());
 }
 
@@ -72,6 +75,7 @@
        DoesNotRegisterClientsInAshForNonPrimaryUser) {
   auto* const controller = Shell::Get()->glanceables_v2_controller();
   auto service = std::make_unique<GlanceablesKeyedService>(profile());
+  EXPECT_TRUE(controller->GetClassroomClient());
   EXPECT_TRUE(controller->GetTasksClient());
 
   const auto first_account_id = AccountId::FromUserEmail(kPrimaryProfileName);
@@ -84,9 +88,11 @@
   session_controller_client()->AddUserSession(kSecondaryProfileName);
 
   session_controller_client()->SwitchActiveUser(second_account_id);
+  EXPECT_FALSE(controller->GetClassroomClient());
   EXPECT_FALSE(controller->GetTasksClient());
 
   session_controller_client()->SwitchActiveUser(first_account_id);
+  EXPECT_TRUE(controller->GetClassroomClient());
   EXPECT_TRUE(controller->GetTasksClient());
 }
 
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc
index 79a89a0..5f5d165 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc
@@ -9,14 +9,12 @@
 #include <string>
 #include <vector>
 
-#include "ash/constants/ash_features.h"
 #include "ash/glanceables/tasks/glanceables_tasks_types.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/test/bind.h"
 #include "base/test/repeating_test_future.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "content/public/test/browser_task_environment.h"
@@ -171,7 +169,6 @@
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::IO};
   net::EmbeddedTestServer test_server_;
-  base::test::ScopedFeatureList feature_list_{features::kGlanceablesV2};
   scoped_refptr<network::TestSharedURLLoaderFactory> url_loader_factory_ =
       base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
           /*network_service=*/nullptr,
diff --git a/chrome/browser/ui/ash/login_screen_client_impl.cc b/chrome/browser/ui/ash/login_screen_client_impl.cc
index 99f12a70..6ec4ab0 100644
--- a/chrome/browser/ui/ash/login_screen_client_impl.cc
+++ b/chrome/browser/ui/ash/login_screen_client_impl.cc
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/child_accounts/parent_access_controller.h"
 #include "ash/public/cpp/login_screen.h"
 #include "ash/public/cpp/login_screen_model.h"
+#include "base/check_is_test.h"
 #include "base/functional/bind.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
@@ -21,6 +22,7 @@
 #include "chrome/browser/ash/login/login_auth_recorder.h"
 #include "chrome/browser/ash/login/login_pref_names.h"
 #include "chrome/browser/ash/login/reauth_stats.h"
+#include "chrome/browser/ash/login/screens/user_selection_screen.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/ui/login_display_host_webui.h"
@@ -59,9 +61,20 @@
 
   DCHECK(!g_login_screen_client_instance);
   g_login_screen_client_instance = this;
+
+  if (user_manager::UserManager::IsInitialized()) {
+    user_manager::UserManager::Get()->AddObserver(this);
+  } else {
+    CHECK_IS_TEST();
+  }
 }
 
 LoginScreenClientImpl::~LoginScreenClientImpl() {
+  if (user_manager::UserManager::IsInitialized()) {
+    user_manager::UserManager::Get()->RemoveObserver(this);
+  } else {
+    CHECK_IS_TEST();
+  }
   ash::LoginScreen::Get()->SetClient(nullptr);
   DCHECK_EQ(this, g_login_screen_client_instance);
   g_login_screen_client_instance = nullptr;
@@ -385,6 +398,12 @@
   return nullptr;
 }
 
+void LoginScreenClientImpl::OnUserImageChanged(const user_manager::User& user) {
+  ash::LoginScreen::Get()->GetModel()->SetAvatarForUser(
+      user.GetAccountId(),
+      ash::UserSelectionScreen::BuildAshUserAvatarForUser(user));
+}
+
 void LoginScreenClientImpl::OnParentAccessValidation(
     const AccountId& prefilled_account,
     bool success) {
diff --git a/chrome/browser/ui/ash/login_screen_client_impl.h b/chrome/browser/ui/ash/login_screen_client_impl.h
index 6e913ca..b9b38af 100644
--- a/chrome/browser/ui/ash/login_screen_client_impl.h
+++ b/chrome/browser/ui/ash/login_screen_client_impl.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/browser/ui/ash/login_screen_shown_observer.h"
+#include "components/user_manager/user_manager.h"
 #include "ui/base/ime/ash/input_method_manager.h"
 
 namespace ash {
@@ -24,7 +25,8 @@
 
 // Handles method calls sent from ash to chrome. Also sends messages from chrome
 // to ash.
-class LoginScreenClientImpl : public ash::LoginScreenClient {
+class LoginScreenClientImpl : public ash::LoginScreenClient,
+                              public user_manager::UserManager::Observer {
  public:
   // Handles method calls coming from ash into chrome.
   class Delegate {
@@ -131,6 +133,9 @@
   void OnLoginScreenShown() override;
   views::Widget* GetLoginWindowWidget() override;
 
+  // user_manager::UserManager::Observer:
+  void OnUserImageChanged(const user_manager::User& user) override;
+
  private:
   void SetPublicSessionKeyboardLayout(const AccountId& account_id,
                                       const std::string& locale,
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
index 89b3d50..704627e8 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
@@ -250,13 +250,11 @@
 
 namespace {
 
-const char kArabic[] = "ar";
 const char kFrench[] = "fr";
-const char kChinese[] = "zh-TW";
 const char kUnsupportedLanguage[] = "am";
 const char kSpanishLatam[] = "es-419";
-const char kSpanishMexican[] = "es-mx";
-const char kEnglishNewZealand[] = "en-nz";
+const char kSpanishMexican[] = "es-MX";
+const char kEnglishNewZealand[] = "en-NZ";
 
 bool IsEqualAvailability(const SpeechRecognitionAvailability& first,
                          const SpeechRecognitionAvailability& second) {
@@ -287,14 +285,6 @@
     EXPECT_TRUE(IsEqualAvailability(
         projector_client_->GetSpeechRecognitionAvailability(), availability));
 
-    SetLocale(kArabic);
-    EXPECT_TRUE(IsEqualAvailability(
-        projector_client_->GetSpeechRecognitionAvailability(), availability));
-
-    SetLocale(kChinese);
-    EXPECT_TRUE(IsEqualAvailability(
-        projector_client_->GetSpeechRecognitionAvailability(), availability));
-
     SetLocale(kSpanishLatam);
     EXPECT_TRUE(IsEqualAvailability(
         projector_client_->GetSpeechRecognitionAvailability(), availability));
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
index 5038709..9a566dd7 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
@@ -34,6 +34,7 @@
 #include "chrome/browser/ui/ash/shelf/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/shelf/browser_shortcut_shelf_item_controller.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
+#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/views/crostini/crostini_app_restart_dialog.h"
 #include "chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h"
@@ -350,8 +351,9 @@
     ++index;
   }
 
-  if (ShouldAddPinMenu())
+  if (IsAppPinEditable(app_type_, item().id.app_id, controller()->profile())) {
     AddPinMenu(menu_model.get());
+  }
 
   size_t shortcut_index = menu_items.items.size();
   for (size_t i = index; i < menu_items.items.size(); i++) {
@@ -590,59 +592,6 @@
       extensions::ExtensionPrefs::Get(controller()->profile()), extension);
 }
 
-bool AppServiceShelfContextMenu::ShouldAddPinMenu() {
-  switch (app_type_) {
-    case apps::AppType::kArc: {
-      const arc::ArcAppShelfId& arc_shelf_id =
-          arc::ArcAppShelfId::FromString(item().id.app_id);
-      DCHECK(arc_shelf_id.valid());
-      const ArcAppListPrefs* arc_list_prefs =
-          ArcAppListPrefs::Get(controller()->profile());
-      DCHECK(arc_list_prefs);
-      std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
-          arc_list_prefs->GetApp(arc_shelf_id.app_id());
-      if (!arc_shelf_id.has_shelf_group_id() && app_info->launchable)
-        return true;
-      return false;
-    }
-    case apps::AppType::kPluginVm:
-    case apps::AppType::kBuiltIn: {
-      bool show_in_launcher = false;
-      apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
-          ->AppRegistryCache()
-          .ForOneApp(item().id.app_id,
-                     [&show_in_launcher](const apps::AppUpdate& update) {
-                       show_in_launcher =
-                           update.ShowInLauncher().value_or(false);
-                     });
-      return show_in_launcher;
-    }
-    case apps::AppType::kCrostini:
-    case apps::AppType::kBorealis:
-    case apps::AppType::kChromeApp:
-    case apps::AppType::kWeb:
-    case apps::AppType::kSystemWeb:
-    case apps::AppType::kStandaloneBrowserChromeApp:
-      return true;
-    case apps::AppType::kStandaloneBrowser:
-      // Lacros behaves like the Chrome browser icon and cannot be unpinned.
-      return false;
-    case apps::AppType::kUnknown:
-      // Type kUnknown is used for "unregistered" Crostini apps, which do not
-      // have a .desktop file and can only be closed, not pinned.
-      return false;
-    case apps::AppType::kMacOs:
-    case apps::AppType::kRemote:
-    case apps::AppType::kExtension:
-    case apps::AppType::kStandaloneBrowserExtension:
-      NOTREACHED() << "Type " << (int)app_type_
-                   << " should not appear in shelf.";
-      return false;
-    case apps::AppType::kBruschetta:
-      return true;
-  }
-}
-
 void AppServiceShelfContextMenu::ExecutePublisherContextMenuCommand(
     int command_id) {
   DCHECK(command_id >= ash::LAUNCH_APP_SHORTCUT_FIRST &&
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h
index af450db..bcb2b5e 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h
@@ -78,8 +78,6 @@
   // Helpers to get the launch type for the extension item.
   extensions::LaunchType GetExtensionLaunchType() const;
 
-  bool ShouldAddPinMenu();
-
   void ExecutePublisherContextMenuCommand(int command_id);
 
   apps::AppType app_type_;
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
index ec0bb82..5bff6ea 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
@@ -40,6 +40,7 @@
 #include "chrome/browser/apps/app_service/extension_apps_utils.h"
 #include "chrome/browser/apps/icon_standardizer.h"
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
+#include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
@@ -1309,7 +1310,7 @@
       ++index;
   }
 
-  UpdatePolicyPinnedAppsFromPrefs();
+  UpdateAppsPinStatesFromPrefs();
 
   ReportUpdateShelfIconList(model_);
 }
@@ -1354,9 +1355,10 @@
   return true;
 }
 
-void ChromeShelfController::UpdatePolicyPinnedAppsFromPrefs() {
-  for (int index = 0; index < model_->item_count(); index++)
+void ChromeShelfController::UpdateAppsPinStatesFromPrefs() {
+  for (int index = 0; index < model_->item_count(); index++) {
     UpdatePinnedByPolicyForItemAtIndex(index);
+  }
 }
 
 void ChromeShelfController::UpdatePinnedByPolicyForItemAtIndex(
@@ -1365,6 +1367,7 @@
   const bool pinned_by_policy =
       GetPinnableForAppID(item.id.app_id, profile()) ==
       AppListControllerDelegate::PIN_FIXED;
+
   if (item.pinned_by_policy != pinned_by_policy) {
     item.pinned_by_policy = pinned_by_policy;
     model_->Set(model_index, item);
@@ -1373,6 +1376,23 @@
   ReportUpdateShelfIconList(model_);
 }
 
+void ChromeShelfController::UpdateForcedPinStateForItemAtIndex(
+    int model_index) {
+  ash::ShelfItem item = model_->items()[model_index];
+  auto app_type = apps::AppServiceProxyFactory::GetForProfile(profile())
+                      ->AppRegistryCache()
+                      .GetAppType(item.id.app_id);
+
+  const bool pin_state_forced_by_type =
+      !IsAppPinEditable(app_type, item.id.app_id, profile());
+  if (item.pin_state_forced_by_type != pin_state_forced_by_type) {
+    item.pin_state_forced_by_type = pin_state_forced_by_type;
+    model_->Set(model_index, item);
+  }
+
+  ReportUpdateShelfIconList(model_);
+}
+
 ash::ShelfItemStatus ChromeShelfController::GetAppState(
     const std::string& app_id) {
   for (auto& it : web_contents_to_app_id_) {
@@ -1607,6 +1627,8 @@
       model_->Set(index, item);
   }
 
+  UpdateForcedPinStateForItemAtIndex(index);
+
   // Update the pin position preference as needed.
   if (ShouldSyncItemWithReentrancy(item))
     SyncPinPosition(item.id);
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h
index 8d45988..dde045d 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h
@@ -371,13 +371,18 @@
   // Schedules re-sync of shelf model.
   void ScheduleUpdatePinnedAppsFromSync();
 
-  // Update the policy-pinned flag for each shelf item.
-  void UpdatePolicyPinnedAppsFromPrefs();
+  // Updates the policy-pinned and the forced-pin-state flag for each shelf
+  // item.
+  void UpdateAppsPinStatesFromPrefs();
 
   // Updates the policy-pinned flag for shelf item at `model_index` in shelf
   // model.
   void UpdatePinnedByPolicyForItemAtIndex(int model_index);
 
+  // Updates the pin_state_forced_by_type flag for shelf item at `model_index`
+  // in shelf model.
+  void UpdateForcedPinStateForItemAtIndex(int model_index);
+
   // Returns the shelf item status for the given |app_id|, which can be either
   // STATUS_RUNNING (if there is such an app) or STATUS_CLOSED.
   ash::ShelfItemStatus GetAppState(const std::string& app_id);
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 14bc6b7..9ae0888 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -2431,6 +2431,17 @@
 
 // Test the V1 app interaction flow: run it, pin it, close it, unpin it.
 TEST_F(ChromeShelfControllerTest, V1AppRunPinCloseUnpin) {
+  // Set the app type of extension1_ to a chrome app, as the default unknown app
+  // type is not allowed to be pinned.
+  std::vector<apps::AppPtr> apps;
+  apps::AppPtr app =
+      std::make_unique<apps::App>(apps::AppType::kChromeApp, extension1_->id());
+  apps.push_back(std::move(app));
+  apps::AppServiceProxyFactory::GetForProfile(profile())
+      ->AppRegistryCache()
+      .OnApps(std::move(apps), apps::AppType::kChromeApp,
+              /*should_notify_initialized=*/false);
+
   InitShelfController();
 
   // The model should only contain the browser shortcut.
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc
index d2fb2304..8572b946c 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
+#include "chrome/browser/ui/ash/shelf/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_item_factory.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_prefs.h"
@@ -33,7 +34,6 @@
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "components/services/app_service/public/cpp/app_types.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
@@ -93,6 +93,61 @@
   return AppListControllerDelegate::PIN_EDITABLE;
 }
 
+bool IsAppPinEditable(apps::AppType app_type,
+                      const std::string& app_id,
+                      Profile* profile) {
+  switch (app_type) {
+    case apps::AppType::kArc: {
+      const arc::ArcAppShelfId& arc_shelf_id =
+          arc::ArcAppShelfId::FromString(app_id);
+      DCHECK(arc_shelf_id.valid());
+      const ArcAppListPrefs* arc_list_prefs = ArcAppListPrefs::Get(profile);
+      DCHECK(arc_list_prefs);
+      std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
+          arc_list_prefs->GetApp(arc_shelf_id.app_id());
+      if (!arc_shelf_id.has_shelf_group_id() && app_info &&
+          app_info->launchable) {
+        return true;
+      }
+      return false;
+    }
+    case apps::AppType::kPluginVm:
+    case apps::AppType::kBuiltIn: {
+      bool show_in_launcher = false;
+      apps::AppServiceProxyFactory::GetForProfile(profile)
+          ->AppRegistryCache()
+          .ForOneApp(
+              app_id, [&show_in_launcher](const apps::AppUpdate& update) {
+                show_in_launcher = update.ShowInLauncher().value_or(false);
+              });
+      return show_in_launcher;
+    }
+    case apps::AppType::kCrostini:
+    case apps::AppType::kBorealis:
+    case apps::AppType::kChromeApp:
+    case apps::AppType::kWeb:
+    case apps::AppType::kSystemWeb:
+    case apps::AppType::kStandaloneBrowserChromeApp:
+      return true;
+    case apps::AppType::kStandaloneBrowser:
+      // Lacros behaves like the Chrome browser icon and cannot be unpinned.
+      return false;
+    case apps::AppType::kUnknown:
+      // Type kUnknown is used for "unregistered" Crostini apps, which do not
+      // have a .desktop file and can only be closed, not pinned.
+      return false;
+    case apps::AppType::kMacOs:
+    case apps::AppType::kRemote:
+    case apps::AppType::kExtension:
+    case apps::AppType::kStandaloneBrowserExtension:
+      NOTREACHED() << "Type " << (int)app_type
+                   << " should not appear in shelf.";
+      return false;
+    case apps::AppType::kBruschetta:
+      return true;
+  }
+}
+
 bool IsBrowserRepresentedInBrowserList(Browser* browser,
                                        const ash::ShelfModel* model) {
   // Only Ash desktop browser windows for the active user are represented.
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h
index a3fb805..b41dd662 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/shelf_types.h"
 #include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 
 class Browser;
 
@@ -31,6 +32,12 @@
     const std::string& app_id,
     Profile* profile);
 
+// Whether the pin state of the app with `app_id` is editable according to its
+// `app_type`.
+bool IsAppPinEditable(apps::AppType app_type,
+                      const std::string& app_id,
+                      Profile* profile);
+
 // Returns true when the given |browser| is listed in the browser application
 // list.
 bool IsBrowserRepresentedInBrowserList(Browser* browser,
diff --git a/chrome/browser/ui/ash/shelf/shelf_context_menu.cc b/chrome/browser/ui/ash/shelf/shelf_context_menu.cc
index 6e43b73..5253731 100644
--- a/chrome/browser/ui/ash/shelf/shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/shelf/shelf_context_menu.cc
@@ -113,8 +113,8 @@
 
 bool ShelfContextMenu::IsCommandIdEnabled(int command_id) const {
   if (command_id == ash::TOGGLE_PIN) {
-    // Users cannot modify the pinned state of apps pinned by policy.
-    return !item_.pinned_by_policy &&
+    // Users cannot modify apps with a forced pinned state.
+    return !item_.IsPinStateForced() &&
            (item_.type == ash::TYPE_PINNED_APP || item_.type == ash::TYPE_APP);
   }
 
diff --git a/chrome/browser/ui/ash/user_education/chrome_user_education_delegate.cc b/chrome/browser/ui/ash/user_education/chrome_user_education_delegate.cc
index 3520eb2..41eea2bb 100644
--- a/chrome/browser/ui/ash/user_education/chrome_user_education_delegate.cc
+++ b/chrome/browser/ui/ash/user_education/chrome_user_education_delegate.cc
@@ -4,8 +4,10 @@
 
 #include "chrome/browser/ui/ash/user_education/chrome_user_education_delegate.h"
 
+#include "ash/user_education/user_education_types.h"
 #include "ash/user_education/user_education_util.h"
-#include "base/notreached.h"
+#include "base/check.h"
+#include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -14,9 +16,12 @@
 #include "chrome/browser/ui/views/user_education/browser_user_education_service.h"
 #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
 #include "components/account_id/account_id.h"
+#include "components/user_education/common/help_bubble_factory_registry.h"
+#include "components/user_education/common/help_bubble_params.h"
 #include "components/user_education/common/tutorial_registry.h"
 #include "components/user_education/common/tutorial_service.h"
 #include "components/user_manager/user_manager.h"
+#include "ui/base/interaction/element_tracker.h"
 
 namespace {
 
@@ -41,7 +46,6 @@
 
 ChromeUserEducationDelegate::~ChromeUserEducationDelegate() = default;
 
-// TODO(http://b/279040829): Implement.
 std::unique_ptr<user_education::HelpBubble>
 ChromeUserEducationDelegate::CreateHelpBubble(
     const AccountId& account_id,
@@ -49,8 +53,32 @@
     user_education::HelpBubbleParams help_bubble_params,
     ui::ElementIdentifier element_id,
     ui::ElementContext element_context) {
-  NOTIMPLEMENTED();
-  return nullptr;
+  Profile* profile = Profile::FromBrowserContext(
+      ash::BrowserContextHelper::Get()->GetBrowserContextByAccountId(
+          account_id));
+
+  // NOTE: User education in Ash is currently only supported for the primary
+  // user profile. This is a self-imposed restriction.
+  CHECK(IsPrimaryProfile(profile));
+
+  // If a tracked `element` cannot be found for the specified `element_id` and
+  // `element_context` pair, there's nothing to anchor a help bubble to.
+  ui::TrackedElement* const element =
+      ui::ElementTracker::GetElementTracker()->GetFirstMatchingElement(
+          element_id, element_context);
+  if (!element) {
+    return nullptr;
+  }
+
+  // Help bubble factories expect `help_bubble_id` to be provided via extended
+  // properties being as it is a ChromeOS specific platform construct.
+  help_bubble_params.extended_properties.values().Merge(std::move(
+      ash::user_education_util::CreateExtendedProperties(help_bubble_id)
+          .values()));
+
+  return UserEducationServiceFactory::GetForProfile(profile)
+      ->help_bubble_factory_registry()
+      .CreateHelpBubble(element, std::move(help_bubble_params));
 }
 
 void ChromeUserEducationDelegate::RegisterTutorial(
@@ -63,7 +91,7 @@
 
   // NOTE: User education in Ash is currently only supported for the primary
   // user profile. This is a self-imposed restriction.
-  DCHECK(IsPrimaryProfile(profile));
+  CHECK(IsPrimaryProfile(profile));
 
   UserEducationServiceFactory::GetForProfile(profile)
       ->tutorial_registry()
@@ -83,7 +111,7 @@
 
   // NOTE: User education in Ash is currently only supported for the primary
   // user profile. This is a self-imposed restriction.
-  DCHECK(IsPrimaryProfile(profile));
+  CHECK(IsPrimaryProfile(profile));
 
   UserEducationServiceFactory::GetForProfile(profile)
       ->tutorial_service()
diff --git a/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc b/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc
index b804182..150b7da 100644
--- a/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc
+++ b/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc
@@ -4,10 +4,11 @@
 
 #include "chrome/browser/ui/ash/user_education/chrome_user_education_delegate.h"
 
-#include <vector>
+#include <memory>
 
 #include "ash/session/test_session_controller_client.h"
 #include "ash/test/ash_test_helper.h"
+#include "ash/user_education/user_education_class_properties.h"
 #include "ash/user_education/user_education_types.h"
 #include "ash/user_education/user_education_util.h"
 #include "base/functional/callback.h"
@@ -31,29 +32,22 @@
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/element_test_util.h"
 #include "ui/base/interaction/interaction_sequence.h"
+#include "ui/views/interaction/element_tracker_views.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+#include "ui/views/widget/widget.h"
 
 namespace {
 
-// Helpers ---------------------------------------------------------------------
-
-std::vector<ash::TutorialId> GetTutorialIds() {
-  std::vector<ash::TutorialId> tutorial_ids;
-  for (size_t i = static_cast<size_t>(ash::TutorialId::kMinValue);
-       i <= static_cast<size_t>(ash::TutorialId::kMaxValue); ++i) {
-    tutorial_ids.emplace_back(static_cast<ash::TutorialId>(i));
-  }
-  return tutorial_ids;
-}
+// Element identifiers.
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementId);
 
 }  // namespace
 
 // ChromeUserEducationDelegateTest ---------------------------------------------
 
-// Base class for tests of the `ChromeUserEducationDelegate` parameterized by
-// user education tutorial ID.
-class ChromeUserEducationDelegateTest
-    : public BrowserWithTestWindowTest,
-      public testing::WithParamInterface<ash::TutorialId> {
+// Base class for tests of the `ChromeUserEducationDelegate`.
+class ChromeUserEducationDelegateTest : public BrowserWithTestWindowTest {
  public:
   ChromeUserEducationDelegateTest()
       : user_manager_(new ash::FakeChromeUserManager()),
@@ -69,9 +63,6 @@
   // Returns a pointer to the `delegate_` instance under test.
   ash::UserEducationDelegate* delegate() { return delegate_.get(); }
 
-  // Returns the tutorial ID associated with test parameterization.
-  ash::TutorialId tutorial_id() const { return GetParam(); }
-
  private:
   // BrowserWithTestWindowTest:
   void SetUp() override {
@@ -107,15 +98,46 @@
   std::unique_ptr<ChromeUserEducationDelegate> delegate_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         ChromeUserEducationDelegateTest,
-                         testing::ValuesIn(GetTutorialIds()));
-
 // Tests -----------------------------------------------------------------------
 
+// Verifies `CreateHelpBubble()` is working as intended.
+TEST_F(ChromeUserEducationDelegateTest, CreateHelpBubble) {
+  // Create and show a `widget`.
+  views::Widget widget;
+  views::Widget::InitParams params;
+  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+  widget.Init(std::move(params));
+  widget.SetContentsView(std::make_unique<views::View>());
+  widget.CenterWindow(gfx::Size(100, 100));
+  widget.Show();
+
+  // Cache `element_context` for the widget.
+  const ui::ElementContext element_context =
+      views::ElementTrackerViews::GetContextForWidget(&widget);
+
+  // Verify that a help bubble is *not* created for the specified `kElementId`
+  // and `element_context` pair since no tracked element matching that pair has
+  // been registered with the element tracker framework.
+  EXPECT_FALSE(delegate()->CreateHelpBubble(
+      account_id(), ash::HelpBubbleId::kTest,
+      user_education::HelpBubbleParams(), kElementId, element_context));
+
+  // Register the `widget`s contents `view` with the element tracker framework.
+  views::View* const view = widget.GetContentsView();
+  view->SetProperty(ash::kHelpBubbleContextKey, ash::HelpBubbleContext::kAsh);
+  view->SetProperty(views::kElementIdentifierKey, kElementId);
+
+  // Verify that a help bubble *is* created for the specified `kElementId` and
+  // `element_context` pair.
+  EXPECT_TRUE(delegate()->CreateHelpBubble(
+      account_id(), ash::HelpBubbleId::kTest,
+      user_education::HelpBubbleParams(), kElementId, element_context));
+}
+
 // Verifies `RegisterTutorial()` registers a tutorial with the browser registry.
-TEST_P(ChromeUserEducationDelegateTest, RegisterTutorial) {
-  const ash::TutorialId tutorial_id = this->tutorial_id();
+TEST_F(ChromeUserEducationDelegateTest, RegisterTutorial) {
+  const ash::TutorialId tutorial_id = ash::TutorialId::kTest;
   const auto tutorial_id_str = ash::user_education_util::ToString(tutorial_id);
 
   // Initially there should be no tutorial registered.
@@ -133,13 +155,10 @@
 }
 
 // Verifies `StartTutorial()` starts a tutorial with the browser service.
-TEST_P(ChromeUserEducationDelegateTest, StartTutorial) {
-  const ash::TutorialId tutorial_id = this->tutorial_id();
-
+TEST_F(ChromeUserEducationDelegateTest, StartTutorial) {
   // Create a test element.
-  const ui::ElementContext kElementContext(1);
-  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementId);
-  ui::test::TestElement test_element(kElementId, kElementContext);
+  const ui::ElementContext element_context(1);
+  ui::test::TestElement test_element(kElementId, element_context);
 
   // Create a tutorial description.
   user_education::TutorialDescription tutorial_description;
@@ -149,7 +168,7 @@
       /*element_name=*/std::string(), user_education::HelpBubbleArrow::kNone);
 
   // Register the tutorial.
-  delegate()->RegisterTutorial(account_id(), tutorial_id,
+  delegate()->RegisterTutorial(account_id(), ash::TutorialId::kTest,
                                std::move(tutorial_description));
 
   // Verify the tutorial is not running.
@@ -158,7 +177,8 @@
   EXPECT_FALSE(tutorial_service.IsRunningTutorial());
 
   // Attempt to start the tutorial.
-  delegate()->StartTutorial(account_id(), tutorial_id, kElementContext,
+  delegate()->StartTutorial(account_id(), ash::TutorialId::kTest,
+                            element_context,
                             /*completed_callback=*/base::DoNothing(),
                             /*aborted_callback=*/base::DoNothing());
 
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index ca20ad2..916d3005 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -41,7 +41,7 @@
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/passwords/ui_utils.h"
 #include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
-#include "chrome/browser/ui/side_panel/side_panel_open_trigger.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
diff --git a/chrome/browser/ui/browser_list.cc b/chrome/browser/ui/browser_list.cc
index 0e12228..c16e80d 100644
--- a/chrome/browser/ui/browser_list.cc
+++ b/chrome/browser/ui/browser_list.cc
@@ -54,7 +54,7 @@
 }  // namespace
 
 // static
-base::LazyInstance<base::ObserverList<BrowserListObserver>::Unchecked>::Leaky
+base::LazyInstance<base::ObserverList<BrowserListObserver>>::Leaky
     BrowserList::observers_ = LAZY_INSTANCE_INITIALIZER;
 
 // static
diff --git a/chrome/browser/ui/browser_list.h b/chrome/browser/ui/browser_list.h
index 8daa76b2..9f1e9b5 100644
--- a/chrome/browser/ui/browser_list.h
+++ b/chrome/browser/ui/browser_list.h
@@ -220,8 +220,8 @@
 
   // A list of observers which will be notified of every browser addition and
   // removal across all BrowserLists.
-  static base::LazyInstance<
-      base::ObserverList<BrowserListObserver>::Unchecked>::Leaky observers_;
+  static base::LazyInstance<base::ObserverList<BrowserListObserver>>::Leaky
+      observers_;
 
   static BrowserList* instance_;
 };
diff --git a/chrome/browser/ui/browser_list_observer.h b/chrome/browser/ui/browser_list_observer.h
index d8a38d0..95df718c 100644
--- a/chrome/browser/ui/browser_list_observer.h
+++ b/chrome/browser/ui/browser_list_observer.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_BROWSER_LIST_OBSERVER_H_
 #define CHROME_BROWSER_UI_BROWSER_LIST_OBSERVER_H_
 
+#include "base/observer_list_types.h"
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_ANDROID)
@@ -13,7 +14,7 @@
 
 class Browser;
 
-class BrowserListObserver {
+class BrowserListObserver : public base::CheckedObserver {
  public:
   // Called immediately after a browser is added to the list
   virtual void OnBrowserAdded(Browser* browser) {}
@@ -30,9 +31,6 @@
 
   // Called immediately after a browser becomes not active.
   virtual void OnBrowserNoLongerActive(Browser* browser) {}
-
- protected:
-  virtual ~BrowserListObserver() {}
 };
 
 #endif  // CHROME_BROWSER_UI_BROWSER_LIST_OBSERVER_H_
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index 0241532..a5b5076c 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -26,7 +26,7 @@
 #include "chrome/browser/ui/hats/hats_service.h"
 #include "chrome/browser/ui/page_action/page_action_icon_type.h"
 #include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
-#include "chrome/browser/ui/side_panel/side_panel_open_trigger.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
 #include "chrome/common/buildflags.h"
 #include "components/content_settings/core/common/content_settings_types.h"
diff --git a/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm b/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
index fd5e5a4..0916a11f 100644
--- a/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
+++ b/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
@@ -313,7 +313,7 @@
 
   // Add 3 rows above the selection. The selected tab should not change.
   for (int i = 0; i < 3; ++i) {
-    ASSERT_TRUE(content::ExecuteScript(tabs[0], "window.open('title3.html');"));
+    ASSERT_TRUE(content::ExecJs(tabs[0], "window.open('title3.html');"));
     EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
   }
   ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 3), pattern));
@@ -322,7 +322,7 @@
 
   // Add 2 rows below the selection. The selected tab should not change.
   for (int i = 0; i < 2; ++i) {
-    ASSERT_TRUE(content::ExecuteScript(tabs[2], "window.open('title3.html');"));
+    ASSERT_TRUE(content::ExecJs(tabs[2], "window.open('title3.html');"));
     EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
   }
   ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 2), pattern));
@@ -331,7 +331,7 @@
 
   // Add a new row in the same process as the selection. The selected tab should
   // not change.
-  ASSERT_TRUE(content::ExecuteScript(tabs[1], "window.open('title3.html');"));
+  ASSERT_TRUE(content::ExecJs(tabs[1], "window.open('title3.html');"));
   EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
   ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 1), pattern));
   EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[1]));
@@ -416,7 +416,7 @@
   // Add two new tasks to the same process as a.com
   int num_group_tasks = 1;
   for (int i = 0; i < 2; i++) {
-    ASSERT_TRUE(content::ExecuteScript(tabs[0], "window.open('title3.html');"));
+    ASSERT_TRUE(content::ExecJs(tabs[0], "window.open('title3.html');"));
     EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[0]));
     ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows((rows += 1), pattern));
     EXPECT_EQ(TableFirstSelectedRow(), FindRowForTab(tabs[0]));
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 1b3cbab..3dfcf94 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -22,6 +22,8 @@
   E_CPONLY(kColorAppMenuHighlightSeverityMedium) \
   E_CPONLY(kColorAppMenuHighlightDefault) \
   E_CPONLY(kColorAppMenuExpandedForegroundDefault) \
+  E_CPONLY(kColorAppMenuChipInkDropHover) \
+  E_CPONLY(kColorAppMenuChipInkDropRipple) \
   /* Avatar colors. */ \
   E_CPONLY(kColorAvatarButtonHighlightNormal) \
   E_CPONLY(kColorAvatarButtonHighlightSyncError) \
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index 1cf316e..301eea0 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -679,6 +679,8 @@
       ui::SetAlpha(kColorToolbarInkDrop, kToolbarInkDropHighlightVisibleAlpha);
   mixer[kColorToolbarInkDropRipple] =
       ui::SetAlpha(kColorToolbarInkDrop, std::ceil(0.06f * 255.0f));
+  mixer[kColorAppMenuChipInkDropHover] = {kColorToolbarInkDropHover};
+  mixer[kColorAppMenuChipInkDropRipple] = {kColorToolbarInkDropRipple};
   mixer[kColorToolbarExtensionSeparatorEnabled] = {
       kColorTabBackgroundInactiveFrameActive};
   mixer[kColorToolbarExtensionSeparatorDisabled] = {
diff --git a/chrome/browser/ui/color/material_chrome_color_mixer.cc b/chrome/browser/ui/color/material_chrome_color_mixer.cc
index 64f3e1f..687db34 100644
--- a/chrome/browser/ui/color/material_chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/material_chrome_color_mixer.cc
@@ -30,6 +30,8 @@
   mixer[kColorAppMenuHighlightSeverityHigh] = {kColorAppMenuHighlightDefault};
   mixer[kColorAppMenuExpandedForegroundDefault] = {
       ui::kColorSysOnTonalContainer};
+  mixer[kColorAppMenuChipInkDropHover] = {ui::kColorSysStateHoverOnSubtle};
+  mixer[kColorAppMenuChipInkDropRipple] = {ui::kColorSysStateRipplePrimary};
   mixer[kColorAvatarButtonHighlightNormal] =
       AdjustHighlightColorForContrast(ui::kColorSysPrimary, kColorToolbar);
   mixer[kColorBookmarkBarBackground] = {ui::kColorSysBase};
diff --git a/chrome/browser/ui/layout_constants.cc b/chrome/browser/ui/layout_constants.cc
index af31706..b5bc899b 100644
--- a/chrome/browser/ui/layout_constants.cc
+++ b/chrome/browser/ui/layout_constants.cc
@@ -157,7 +157,7 @@
 
     case TOOLBAR_INTERIOR_MARGIN:
       if (base::FeatureList::IsEnabled(features::kChromeRefresh2023)) {
-        return touch_ui ? gfx::Insets() : gfx::Insets::VH(6, 3);
+        return touch_ui ? gfx::Insets() : gfx::Insets::VH(6, 5);
       } else {
         return touch_ui ? gfx::Insets() : gfx::Insets::VH(4, 8);
       }
diff --git a/chrome/browser/ui/lens/lens_core_tab_side_panel_helper.h b/chrome/browser/ui/lens/lens_core_tab_side_panel_helper.h
index 26dedc9..ec7c64bb 100644
--- a/chrome/browser/ui/lens/lens_core_tab_side_panel_helper.h
+++ b/chrome/browser/ui/lens/lens_core_tab_side_panel_helper.h
@@ -7,6 +7,7 @@
 
 #include "components/search_engines/template_url_service.h"
 #include "content/public/browser/web_contents.h"
+#include "ui/gfx/geometry/size.h"
 
 namespace lens {
 
@@ -23,6 +24,11 @@
 
 }  // namespace internal
 
+// Returns the upper bound of the initial content area size of the side panel
+// if the Lens side panel were to be opened or used right now.
+gfx::Size GetSidePanelInitialContentSizeUpperBound(
+    content::WebContents* web_contents);
+
 // Returns if the v2 unified side panel is enabled when Google is the default
 // search engine.
 bool IsSidePanelEnabledForLens(content::WebContents* web_contents);
diff --git a/chrome/browser/ui/login/login_handler_browsertest.cc b/chrome/browser/ui/login/login_handler_browsertest.cc
index b02fa59..e140568 100644
--- a/chrome/browser/ui/login/login_handler_browsertest.cc
+++ b/chrome/browser/ui/login/login_handler_browsertest.cc
@@ -48,6 +48,7 @@
 #include "content/public/test/prerender_test_util.h"
 #include "content/public/test/slow_http_response.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
 #include "net/base/auth.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -336,8 +337,11 @@
 const char kSingleRealmTestPage[] = "/login/single_realm.html";
 
 const char kAuthBasicPage[] = "/auth-basic";
+const char kAuthBasicSubframePage[] = "/auth-basic-subframe.html";
 const char kAuthDigestPage[] = "/auth-digest";
 
+const char kTitlePage[] = "/title1.html";
+
 // It does not matter what pages are selected as no-auth, as long as they exist.
 // Navigating to non-existing pages caused flakes in the past
 // (https://crbug.com/636875).
@@ -439,6 +443,124 @@
   }
 }
 
+// Test that a BasicAuth prompt from the main frame prevents the page from
+// entering back/forward cache but that a successful authentication does not.
+IN_PROC_BROWSER_TEST_P(LoginPromptBrowserTest,
+                       TestBasicAuthPromptBlocksBackForwardCache) {
+  // Don't run this test if BackForwardCache is disabled.
+  if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
+    return;
+  }
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL test_page = embedded_test_server()->GetURL(kAuthBasicPage);
+  GURL title_page = embedded_test_server()->GetURL("a.com", kTitlePage);
+
+  content::WebContents* contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  NavigationController* controller = &contents->GetController();
+  LoginPromptBrowserTestObserver observer;
+  observer.Register(content::Source<NavigationController>(controller));
+
+  // Navigate to the page and wait for the auth prompt.
+  {
+    WindowedAuthNeededObserver auth_needed_waiter(controller);
+    browser()->OpenURL(OpenURLParams(test_page, Referrer(),
+                                     WindowOpenDisposition::CURRENT_TAB,
+                                     ui::PAGE_TRANSITION_TYPED, false));
+    auth_needed_waiter.Wait();
+  }
+  content::RenderFrameHostWrapper rfh(contents->GetPrimaryMainFrame());
+
+  // Navigate away.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), title_page));
+
+  // We expect the previous page to be destroyed without entering back/forward
+  // cache.
+  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
+
+  // Go back to the page and wait for the auth prompt.
+  {
+    WindowedAuthNeededObserver auth_needed_waiter(controller);
+    controller->GoBack();
+    auth_needed_waiter.Wait();
+  }
+
+  // Complete the authentication.
+  SetAuthForAndWait(*observer.handlers().begin(), controller);
+  ExpectSuccessfulBasicAuthTitle(contents);
+
+  // Navigate away and go back again.
+  content::RenderFrameHostWrapper rfh2(contents->GetPrimaryMainFrame());
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), title_page));
+  ASSERT_TRUE(content::HistoryGoBack(contents));
+
+  // This time the page should have been restored from the cache.
+  ASSERT_EQ(rfh2.get(), contents->GetPrimaryMainFrame());
+}
+
+// Test that a BasicAuth prompt from a subframe prevents the page from
+// entering back/forward cache.
+IN_PROC_BROWSER_TEST_P(LoginPromptBrowserTest,
+                       TestBasicAuthPromptSubframeBlocksBackForwardCache) {
+  // Don't run this test if BackForwardCache is disabled.
+  if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
+    return;
+  }
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL test_page = embedded_test_server()->GetURL(kAuthBasicSubframePage);
+  GURL title_page = embedded_test_server()->GetURL("a.com", kTitlePage);
+
+  content::WebContents* contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  NavigationController* controller = &contents->GetController();
+  LoginPromptBrowserTestObserver observer;
+  observer.Register(content::Source<NavigationController>(controller));
+
+  // Navigate to the page and wait for the auth prompt.
+  {
+    WindowedAuthNeededObserver auth_needed_waiter(controller);
+    browser()->OpenURL(OpenURLParams(test_page, Referrer(),
+                                     WindowOpenDisposition::CURRENT_TAB,
+                                     ui::PAGE_TRANSITION_TYPED, false));
+    auth_needed_waiter.Wait();
+  }
+  content::RenderFrameHostWrapper rfh(contents->GetPrimaryMainFrame());
+
+  // Navigate away.
+  // TODO(https://crbug.com/1444329): Use `NavigateToURL`.
+  WindowedAuthCancelledObserver auth_cancelled_waiter(controller);
+  ASSERT_TRUE(content::NavigateToURLFromRenderer(contents, title_page));
+  auth_cancelled_waiter.Wait();
+
+  // We expect the previous page to be destroyed without entering back/forward
+  // cache.
+  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
+
+  // Go back to the page and wait for the auth prompt.
+  {
+    WindowedAuthNeededObserver auth_needed_waiter(controller);
+    controller->GoBack();
+    auth_needed_waiter.Wait();
+  }
+
+  // Complete the authentication.
+  SetAuthForAndWait(*observer.handlers().begin(), controller);
+  content::WaitForLoadStop(contents);
+  ASSERT_EQ(ExpectedTitleFromAuth(u"basicuser", u"secret"),
+            content::EvalJs(contents, "subframe.contentDocument.title"));
+
+  // Navigate away and go back again.
+  content::RenderFrameHostWrapper rfh2(contents->GetPrimaryMainFrame());
+  // TODO(https://crbug.com/1444329): Use `NavigateToURL`.
+  ASSERT_TRUE(content::NavigateToURLFromRenderer(contents, title_page));
+  ASSERT_TRUE(content::HistoryGoBack(contents));
+
+  // This time the page should have been restored from the cache.
+  ASSERT_EQ(rfh2.get(), contents->GetPrimaryMainFrame());
+}
+
 // Test that "Digest" HTTP authentication works.
 IN_PROC_BROWSER_TEST_P(LoginPromptBrowserTest, TestDigestAuth) {
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/ui/media_router/media_route_starter.h b/chrome/browser/ui/media_router/media_route_starter.h
index 5d15000..9220f18 100644
--- a/chrome/browser/ui/media_router/media_route_starter.h
+++ b/chrome/browser/ui/media_router/media_route_starter.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
diff --git a/chrome/browser/ui/side_panel/BUILD.gn b/chrome/browser/ui/side_panel/BUILD.gn
new file mode 100644
index 0000000..18f01b3
--- /dev/null
+++ b/chrome/browser/ui/side_panel/BUILD.gn
@@ -0,0 +1,7 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD - style license that can be
+# found in the LICENSE file.
+
+source_set("side_panel_enums") {
+  sources = [ "side_panel_enums.h" ]
+}
diff --git a/chrome/browser/ui/side_panel/companion/OWNERS b/chrome/browser/ui/side_panel/companion/OWNERS
index 6e913aa..8fa4df68 100644
--- a/chrome/browser/ui/side_panel/companion/OWNERS
+++ b/chrome/browser/ui/side_panel/companion/OWNERS
@@ -1,2 +1 @@
-file://components/lens/OWNERS
-
+file://chrome/browser/companion/OWNERS
diff --git a/chrome/browser/ui/side_panel/companion/companion_tab_helper.cc b/chrome/browser/ui/side_panel/companion/companion_tab_helper.cc
index fa9248e6..73901ad 100644
--- a/chrome/browser/ui/side_panel/companion/companion_tab_helper.cc
+++ b/chrome/browser/ui/side_panel/companion/companion_tab_helper.cc
@@ -36,7 +36,8 @@
     const GURL& search_url) {
   CHECK(delegate_);
   SetTextQuery(GetTextQueryFromSearchUrl(search_url));
-  delegate_->ShowCompanionSidePanel();
+  delegate_->ShowCompanionSidePanel(
+      SidePanelOpenTrigger::kContextMenuSearchOption);
 }
 
 void CompanionTabHelper::ShowCompanionSidePanelForImage(
@@ -76,7 +77,7 @@
   }
 
   // Show the side panel.
-  delegate_->ShowCompanionSidePanel();
+  delegate_->ShowCompanionSidePanel(SidePanelOpenTrigger::kLensContextMenu);
 }
 
 GURL CompanionTabHelper::SetImageTranslateQueryParams(GURL upload_url) {
@@ -176,6 +177,18 @@
 #endif
 }
 
+void CompanionTabHelper::SetMostRecentSidePanelOpenTrigger(
+    absl::optional<SidePanelOpenTrigger> side_panel_open_trigger) {
+  side_panel_open_trigger_ = side_panel_open_trigger;
+}
+
+absl::optional<SidePanelOpenTrigger>
+CompanionTabHelper::GetAndResetMostRecentSidePanelOpenTrigger() {
+  auto copy = side_panel_open_trigger_;
+  side_panel_open_trigger_ = absl::nullopt;
+  return copy;
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(CompanionTabHelper);
 
 }  // namespace companion
diff --git a/chrome/browser/ui/side_panel/companion/companion_tab_helper.h b/chrome/browser/ui/side_panel/companion/companion_tab_helper.h
index 5a475b8..3b04e513 100644
--- a/chrome/browser/ui/side_panel/companion/companion_tab_helper.h
+++ b/chrome/browser/ui/side_panel/companion/companion_tab_helper.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_SIDE_PANEL_COMPANION_COMPANION_TAB_HELPER_H_
 
 #include "chrome/browser/companion/core/mojom/companion.mojom.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "components/lens/buildflags.h"
 #include "content/public/browser/web_contents_user_data.h"
 
@@ -37,7 +38,8 @@
     // Deregisters the companion SidePanelEntry.
     virtual void DeregisterEntry() = 0;
     // Shows the companion side panel.
-    virtual void ShowCompanionSidePanel() = 0;
+    virtual void ShowCompanionSidePanel(
+        SidePanelOpenTrigger side_panel_open_trigger) = 0;
     // Triggers an update of the 'open in new tab' button.
     virtual void UpdateNewTabButton(GURL url_to_open) = 0;
     // Retrieves the web contents for testing purposes.
@@ -92,6 +94,15 @@
   // Returns the companion web contents for testing purposes.
   content::WebContents* GetCompanionWebContentsForTesting();
 
+  // For caching entry point metrics.
+  // Called to cache the trigger which is later recorded as metrics as soon as
+  // the companion page opens up.
+  void SetMostRecentSidePanelOpenTrigger(
+      absl::optional<SidePanelOpenTrigger> side_panel_open_trigger);
+  // Called to get the most recent value of trigger and immediately reset it.
+  absl::optional<SidePanelOpenTrigger>
+  GetAndResetMostRecentSidePanelOpenTrigger();
+
  private:
   explicit CompanionTabHelper(content::WebContents* web_contents);
 
@@ -108,6 +119,10 @@
   std::unique_ptr<side_panel::mojom::ImageQuery> image_query_;
   std::unique_ptr<Delegate> delegate_;
   std::string text_query_;
+
+  // Caches the trigger source for an in-progress companion page open action in
+  // the current tab. Should be cleared after the open action is complete.
+  absl::optional<SidePanelOpenTrigger> side_panel_open_trigger_;
 #if BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
   std::unique_ptr<lens::LensRegionSearchController>
       lens_region_search_controller_;
diff --git a/chrome/browser/ui/side_panel/side_panel_open_trigger.h b/chrome/browser/ui/side_panel/side_panel_enums.h
similarity index 80%
rename from chrome/browser/ui/side_panel/side_panel_open_trigger.h
rename to chrome/browser/ui/side_panel/side_panel_enums.h
index d32c471..9f2187f 100644
--- a/chrome/browser/ui/side_panel/side_panel_open_trigger.h
+++ b/chrome/browser/ui/side_panel/side_panel_enums.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 CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_OPEN_TRIGGER_H_
-#define CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_OPEN_TRIGGER_H_
+#ifndef CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_ENUMS_H_
+#define CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_ENUMS_H_
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused. SidePanelOpenTrigger in
@@ -27,4 +27,4 @@
   kMaxValue = kAppMenu,
 };
 
-#endif  // CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_OPEN_TRIGGER_H_
+#endif  // CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_ENUMS_H_
diff --git a/chrome/browser/ui/startup/first_run_service.h b/chrome/browser/ui/startup/first_run_service.h
index e26b6bb3..7476cae 100644
--- a/chrome/browser/ui/startup/first_run_service.h
+++ b/chrome/browser/ui/startup/first_run_service.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/metrics/field_trial.h"
 #include "base/no_destructor.h"
 #include "build/build_config.h"
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 23b8c78..d7e612f 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -397,15 +397,6 @@
   bool open_guest_profile = profiles::IsGuestModeRequested(
       command_line, g_browser_process->local_state(),
       /* show_warning= */ true);
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  if (profiles::IsGuestModeEnabled()) {
-    const auto* init_params = chromeos::BrowserParamsProxy::Get();
-    open_guest_profile =
-        open_guest_profile ||
-        init_params->InitialBrowserAction() ==
-            crosapi::mojom::InitialBrowserAction::kOpenGuestWindow;
-  }
-#endif
   if (open_guest_profile) {
     Profile* profile = g_browser_process->profile_manager()->GetProfile(
         ProfileManager::GetGuestProfilePath());
@@ -424,12 +415,6 @@
     return profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
   } else {
     bool expect_incognito = command_line.HasSwitch(switches::kIncognito);
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-    auto* init_params = chromeos::BrowserParamsProxy::Get();
-    expect_incognito |=
-        init_params->InitialBrowserAction() ==
-        crosapi::mojom::InitialBrowserAction::kOpenIncognitoWindow;
-#endif
     LOG_IF(WARNING, expect_incognito)
         << "Incognito mode disabled by policy, launching a normal "
         << "browser session.";
@@ -845,19 +830,6 @@
   // those as there is nothing to restore.
   bool restore_last_session =
       command_line.HasSwitch(switches::kRestoreLastSession);
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  auto* init_params = chromeos::BrowserParamsProxy::Get();
-  if (init_params->InitialBrowserAction() ==
-          crosapi::mojom::InitialBrowserAction::kOpenNewTabPageWindow ||
-      init_params->InitialBrowserAction() ==
-          crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls) {
-    pref.type = SessionStartupPref::DEFAULT;
-    return pref;
-  }
-  restore_last_session |=
-      init_params->InitialBrowserAction() ==
-      crosapi::mojom::InitialBrowserAction::kRestoreLastSession;
-#endif
   if ((restore_last_session || did_restart) && !profile->IsNewProfile()) {
     pref.type = SessionStartupPref::LAST;
   }
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index f29a829..441d979 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -264,17 +264,14 @@
     params.startup_id =
         command_line_->GetSwitchValueASCII("desktop-startup-id");
 #endif
+    if (command_line_->HasSwitch(switches::kWindowName)) {
+      params.user_title =
+          command_line_->GetSwitchValueASCII(switches::kWindowName);
+    }
+
     browser = Browser::Create(params);
   }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  auto* init_params = chromeos::BrowserParamsProxy::Get();
-  bool from_arc =
-      init_params->InitialBrowserAction() ==
-          crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls &&
-      init_params->StartupUrlsFrom() == crosapi::mojom::OpenUrlFrom::kArc;
-#endif
-
   bool first_tab = true;
   custom_handlers::ProtocolHandlerRegistry* registry =
       profile_ ? ProtocolHandlerRegistryFactory::GetForBrowserContext(profile_)
@@ -320,19 +317,6 @@
 #endif  // BUILDFLAG(ENABLE_RLZ)
 
     Navigate(&params);
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-    if (from_arc) {
-      auto* contents = params.navigated_or_inserted_contents;
-      if (contents) {
-        // Add a flag to remember this tab originated in the ARC context.
-        contents->SetUserData(
-            &arc::ArcWebContentsData::kArcTransitionFlag,
-            std::make_unique<arc::ArcWebContentsData>(contents));
-      }
-    }
-#endif
-
     first_tab = false;
   }
   if (!browser->tab_strip_model()->GetActiveWebContents()) {
@@ -509,15 +493,6 @@
     bool welcome_enabled,
     bool whats_new_enabled,
     bool privacy_sandbox_dialog_required) {
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  {
-    // If URLs are passed via crosapi, forcibly opens those tabs.
-    StartupTabs crosapi_tabs = provider.GetCrosapiTabs();
-    if (!crosapi_tabs.empty())
-      return {std::move(crosapi_tabs), LaunchResult::kWithGivenUrls};
-  }
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
   StartupTabs tabs =
       provider.GetCommandLineTabs(*command_line_, cur_dir_, profile_);
   LaunchResult launch_result =
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl_unittest.cc b/chrome/browser/ui/startup/startup_browser_creator_impl_unittest.cc
index bcfa1f0..77bc4886 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl_unittest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl_unittest.cc
@@ -39,10 +39,6 @@
 constexpr uint32_t kPrivacySandboxTabs = 1 << 10;
 #endif  // !BUILDFLAG(IS_ANDROID)
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-constexpr uint32_t kCrosapiTabs = 1 << 11;
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 class FakeStartupTabProvider : public StartupTabProvider {
  public:
   // For each option passed, the corresponding adder below will add a sentinel
@@ -133,15 +129,6 @@
                                          : CommandLineTabsPresent::kNo;
   }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  StartupTabs GetCrosapiTabs() const override {
-    StartupTabs tabs;
-    if (options_ & kCrosapiTabs)
-      tabs.emplace_back(GURL("https://crosapi"));
-    return tabs;
-  }
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 #if !BUILDFLAG(IS_ANDROID)
   StartupTabs GetNewFeaturesTabs(bool whats_new_enabled) const override {
     StartupTabs tabs;
@@ -384,29 +371,6 @@
   EXPECT_EQ("cmd-line", output.tabs[0].url.host());
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-// If URLs are given via Crosapi, just return it.
-TEST_P(StartupBrowserCreatorImplTest, DetermineStartupTabs_Crosapi) {
-  using LaunchResult = Creator::LaunchResult;
-
-  FakeStartupTabProvider provider(
-      kOnboardingTabs | kDistributionFirstRunTabs | kResetTriggerTabs |
-      kPinnedTabs | kPreferencesTabs | kNewTabPageTabs | kNewFeaturesTabs |
-      kCrosapiTabs | kPrivacySandboxTabs);
-  Creator impl(base::FilePath(),
-               base::CommandLine(base::CommandLine::NO_PROGRAM),
-               chrome::startup::IsFirstRun::kYes);
-
-  auto output = impl.DetermineStartupTabs(
-      provider, chrome::startup::IsProcessStartup::kYes, false, false, false,
-      true, true, true, true);
-  EXPECT_EQ(LaunchResult::kWithGivenUrls, output.launch_result);
-
-  ASSERT_EQ(1U, output.tabs.size());
-  EXPECT_EQ("crosapi", output.tabs[0].url.host());
-}
-#endif
-
 // New Tab Page should appear alongside pinned tabs and the reset trigger, but
 // should be superseded by onboarding tabs and by tabs specified in preferences.
 TEST_P(StartupBrowserCreatorImplTest, DetermineStartupTabs_NewTabPage) {
diff --git a/chrome/browser/ui/startup/startup_tab_provider.cc b/chrome/browser/ui/startup/startup_tab_provider.cc
index 3eed1fb..c617825 100644
--- a/chrome/browser/ui/startup/startup_tab_provider.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider.cc
@@ -53,11 +53,6 @@
 #include "chrome/browser/shell_integration.h"
 #endif  // BUILDFLAG(IS_WIN)
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chromeos/crosapi/mojom/crosapi.mojom.h"
-#include "chromeos/startup/browser_params_proxy.h"
-#endif
-
 #if !BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h"
 #include "chrome/browser/search/search.h"
@@ -284,24 +279,6 @@
                     : CommandLineTabsPresent::kNo;
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-StartupTabs StartupTabProviderImpl::GetCrosapiTabs() const {
-  auto* init_params = chromeos::BrowserParamsProxy::Get();
-  if (init_params->InitialBrowserAction() !=
-          crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls ||
-      !init_params->StartupUrls().has_value()) {
-    return {};
-  }
-
-  StartupTabs result;
-  for (const GURL& url : *init_params->StartupUrls()) {
-    if (ValidateUrl(url))
-      result.emplace_back(url);
-  }
-  return result;
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 #if !BUILDFLAG(IS_ANDROID)
 StartupTabs StartupTabProviderImpl::GetNewFeaturesTabs(
     bool whats_new_enabled) const {
diff --git a/chrome/browser/ui/startup/startup_tab_provider.h b/chrome/browser/ui/startup/startup_tab_provider.h
index 61d0061..548ff40 100644
--- a/chrome/browser/ui/startup/startup_tab_provider.h
+++ b/chrome/browser/ui/startup/startup_tab_provider.h
@@ -95,12 +95,6 @@
       const base::CommandLine& command_line,
       const base::FilePath& cur_dir) const = 0;
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // Returns the URLs given via the crosapi BrowserInitParams with
-  // kOpenWindowWithUrls action.
-  virtual StartupTabs GetCrosapiTabs() const = 0;
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 #if !BUILDFLAG(IS_ANDROID)
   // Returns tabs related to the What's New UI (if applicable).
   virtual StartupTabs GetNewFeaturesTabs(bool whats_new_enabled) const = 0;
@@ -243,10 +237,6 @@
       const base::CommandLine& command_line,
       const base::FilePath& cur_dir) const override;
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  StartupTabs GetCrosapiTabs() const override;
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 #if !BUILDFLAG(IS_ANDROID)
   StartupTabs GetNewFeaturesTabs(bool whats_new_enabled) const override;
   StartupTabs GetPrivacySandboxTabs(
diff --git a/chrome/browser/ui/startup/startup_tab_provider_unittest.cc b/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
index 10b45c0..980d894 100644
--- a/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
@@ -20,11 +20,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chromeos/crosapi/mojom/crosapi.mojom.h"
-#include "chromeos/startup/browser_init_params.h"
-#endif
-
 #if !BUILDFLAG(IS_ANDROID)
 #include "base/values.h"
 #include "extensions/browser/extension_registry.h"
@@ -519,89 +514,6 @@
   }
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-TEST(StartupTabProviderTest, GetCrosapiTabs) {
-  base::test::TaskEnvironment task_environment;
-
-  // Non kOpenWindowWithUrls case.
-  {
-    auto params = crosapi::mojom::BrowserInitParams::New();
-    params->initial_browser_action =
-        crosapi::mojom::InitialBrowserAction::kUseStartupPreference;
-    // The given URLs should be ignored.
-    params->startup_urls = std::vector<GURL>{GURL("https://google.com")};
-    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
-    StartupTabs output = StartupTabProviderImpl().GetCrosapiTabs();
-    EXPECT_TRUE(output.empty());
-  }
-
-  // Simple use. Pass google.com URL.
-  {
-    auto params = crosapi::mojom::BrowserInitParams::New();
-    params->initial_browser_action =
-        crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls;
-    // The given URLs should be ignored.
-    params->startup_urls = std::vector<GURL>{GURL("https://google.com")};
-    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
-    StartupTabs output = StartupTabProviderImpl().GetCrosapiTabs();
-    ASSERT_EQ(1u, output.size());
-    EXPECT_EQ(GURL("https://google.com"), output[0].url);
-  }
-
-  // Two URL case.
-  {
-    auto params = crosapi::mojom::BrowserInitParams::New();
-    params->initial_browser_action =
-        crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls;
-    params->startup_urls = std::vector<GURL>{GURL("https://google.com"),
-                                             GURL("https://gmail.com")};
-    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
-    StartupTabs output = StartupTabProviderImpl().GetCrosapiTabs();
-    ASSERT_EQ(2u, output.size());
-    EXPECT_EQ(GURL("https://google.com"), output[0].url);
-    EXPECT_EQ(GURL("https://gmail.com"), output[1].url);
-  }
-
-  // chrome:// scheme should be allowed on Lacros because calls from
-  // untrustworthy applications are filtered before StartupTabProvider.
-  {
-    auto params = crosapi::mojom::BrowserInitParams::New();
-    params->initial_browser_action =
-        crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls;
-    params->startup_urls = std::vector<GURL>{GURL("chrome://flags")};
-    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
-    StartupTabs output = StartupTabProviderImpl().GetCrosapiTabs();
-    ASSERT_EQ(1u, output.size());
-    EXPECT_EQ(GURL("chrome://flags"), output[0].url);
-  }
-
-  // Exceptional settings page.
-  {
-    auto params = crosapi::mojom::BrowserInitParams::New();
-    params->initial_browser_action =
-        crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls;
-    params->startup_urls =
-        std::vector<GURL>{GURL("chrome://settings/resetProfileSettings")};
-    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
-    StartupTabs output = StartupTabProviderImpl().GetCrosapiTabs();
-    ASSERT_EQ(1u, output.size());
-    EXPECT_EQ(GURL("chrome://settings/resetProfileSettings"), output[0].url);
-  }
-
-  // about:blank URL.
-  {
-    auto params = crosapi::mojom::BrowserInitParams::New();
-    params->initial_browser_action =
-        crosapi::mojom::InitialBrowserAction::kOpenWindowWithUrls;
-    params->startup_urls = std::vector<GURL>{GURL("about:blank")};
-    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
-    StartupTabs output = StartupTabProviderImpl().GetCrosapiTabs();
-    ASSERT_EQ(1u, output.size());
-    EXPECT_EQ(GURL("about:blank"), output[0].url);
-  }
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 #if !BUILDFLAG(IS_ANDROID)
 
 class StartupTabProviderPrivacySandboxTest : public testing::Test {
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper.cc b/chrome/browser/ui/tab_contents/core_tab_helper.cc
index 01e0420..385c4991 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper.cc
+++ b/chrome/browser/ui/tab_contents/core_tab_helper.cc
@@ -173,11 +173,14 @@
                                    bool is_image_translate) {
   TriggerLensPingIfEnabled();
   bool use_side_panel = lens::IsSidePanelEnabledForLens(web_contents());
+
+  const gfx::Size side_panel_initial_size =
+      lens::GetSidePanelInitialContentSizeUpperBound(web_contents());
   SearchByImageImpl(render_frame_host, src_url, kImageSearchThumbnailMinSize,
                     lens::features::GetMaxPixelsForImageSearch(),
                     lens::features::GetMaxPixelsForImageSearch(),
                     lens::GetQueryParametersForLensRequest(
-                        entry_point, use_side_panel,
+                        entry_point, use_side_panel, side_panel_initial_size,
                         /** is_full_screen_region_search_request **/ false,
                         IsImageSearchSupportedForCompanion()),
                     use_side_panel, is_image_translate);
@@ -213,9 +216,11 @@
       lens::IsSidePanelEnabledForLensRegionSearch(web_contents());
   bool is_companion_enabled = IsImageSearchSupportedForCompanion();
 
+  const gfx::Size side_panel_initial_size =
+      lens::GetSidePanelInitialContentSizeUpperBound(web_contents());
   auto lens_query_params = lens::GetQueryParametersForLensRequest(
-      entry_point, use_side_panel, is_full_screen_region_search_request,
-      is_companion_enabled);
+      entry_point, use_side_panel, side_panel_initial_size,
+      is_full_screen_region_search_request, is_companion_enabled);
   SearchByImageImpl(image, image_original_size, lens_query_params,
                     use_side_panel, std::move(log_data));
 }
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper.h b/chrome/browser/ui/tab_contents/core_tab_helper.h
index 8302e16..4553c4f 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper.h
+++ b/chrome/browser/ui/tab_contents/core_tab_helper.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
@@ -100,8 +101,6 @@
  private:
   explicit CoreTabHelper(content::WebContents* web_contents);
   friend class content::WebContentsUserData<CoreTabHelper>;
-  FRIEND_TEST_ALL_PREFIXES(CoreTabHelperWindowUnitTest,
-                           SearchWithLens_LensPingEnabled_TriggersLensPing);
 
   static bool GetStatusTextForWebContents(std::u16string* status_text,
                                           content::WebContents* source);
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper_unittest.cc b/chrome/browser/ui/tab_contents/core_tab_helper_unittest.cc
index 75da6fdd..a6c994d 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper_unittest.cc
+++ b/chrome/browser/ui/tab_contents/core_tab_helper_unittest.cc
@@ -4,20 +4,11 @@
 
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 
-#include "base/memory/raw_ptr.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/search_engines/template_url_service_factory.h"
-#include "chrome/test/base/browser_with_test_window_test.h"
-#include "chrome/test/base/search_test_utils.h"
 #include "components/lens/lens_features.h"
-#include "content/public/browser/navigation_entry.h"
-#include "content/public/test/mock_navigation_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
-// Lens ping URL for tests.
-const char kTestLensPingURL[] = "https://lens-ping.url/";
-
 TEST(CoreTabHelperUnitTest,
      EncodeImageIntoSearchArgs_OptimizedImageFormatsDisabled_EncodesAsPng) {
   base::test::ScopedFeatureList features;
@@ -107,63 +98,3 @@
   EXPECT_EQ("image/png", search_args.image_thumbnail_content_type);
   EXPECT_EQ(lens::mojom::ImageFormat::PNG, image_format);
 }
-
-class CoreTabHelperWindowUnitTest : public BrowserWithTestWindowTest {
- protected:
-  void SetUp() override {
-    BrowserWithTestWindowTest::SetUp();
-    AddTab(browser(), GURL("http://www.google.com/"));
-
-    CoreTabHelper::CreateForWebContents(web_contents());
-    core_tab_helper_ = CoreTabHelper::FromWebContents(web_contents());
-
-    TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-        profile(),
-        base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
-    template_url_service_ = TemplateURLServiceFactory::GetForProfile(profile());
-    search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service_);
-  }
-
-  content::WebContents* web_contents() const {
-    return browser()->tab_strip_model()->GetWebContentsAt(0);
-  }
-
-  content::RenderFrameHost* main_rfh() const {
-    return web_contents()->GetPrimaryMainFrame();
-  }
-
-  content::NavigationController* navigation_controller() {
-    return &(web_contents()->GetController());
-  }
-
- private:
-  raw_ptr<CoreTabHelper> core_tab_helper_;
-  raw_ptr<TemplateURLService> template_url_service_;
-};
-
-TEST_F(CoreTabHelperWindowUnitTest,
-       SearchWithLens_LensPingEnabled_TriggersLensPing) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeatureWithParameters(
-      lens::features::kEnableLensPing, {{"ping-lens-sequentially", "true"},
-                                        {"lens-ping-url", kTestLensPingURL}});
-
-  CoreTabHelper* core_tab_helper =
-      CoreTabHelper::FromWebContents(web_contents());
-  core_tab_helper->SearchWithLens(
-      main_rfh(), GURL(""),
-      lens::EntryPoint::CHROME_SEARCH_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM,
-      false);
-  EXPECT_TRUE(core_tab_helper->awaiting_lens_ping_response_);
-
-  EXPECT_EQ(kTestLensPingURL,
-            navigation_controller()->GetVisibleEntry()->GetURL().spec());
-
-  // Trigger the DidFinishNavigation callback of WebContentsObserver with a
-  // simulated Lens ping response. Purposely do not set the handle committed
-  // flag, as the Lens ping should have a 204 response code.
-  content::MockNavigationHandle simulated_lens_ping_handle(
-      GURL(kTestLensPingURL), main_rfh());
-  core_tab_helper->DidFinishNavigation(&simulated_lens_ping_handle);
-  EXPECT_FALSE(core_tab_helper->awaiting_lens_ping_response_);
-}
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.cc
index ff59203..1d6caf95 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.cc
@@ -110,35 +110,20 @@
     return;
   }
 
-  // If the group already has a local group open, then activate it.
+  // Activate the first tab in a group if it is already open.
   if (saved_group->local_group_id().has_value()) {
-    Browser* browser_for_activation =
-        SavedTabGroupUtils::GetBrowserWithTabGroupId(
-            saved_group->local_group_id().value());
-
-    // Only activate the tab group's first tab if it exists in any browser's
-    // tabstrip model.
-    if (browser_for_activation) {
-      absl::optional<int> first_tab =
-          browser_for_activation->tab_strip_model()
-              ->group_model()
-              ->GetTabGroup(saved_group->local_group_id().value())
-              ->GetFirstTab();
-      DCHECK(first_tab.has_value());
-      browser_for_activation->ActivateContents(
-          browser_for_activation->tab_strip_model()->GetWebContentsAt(
-              first_tab.value()));
-
-      base::RecordAction(
-          base::UserMetricsAction("TabGroups_SavedTabGroups_Focused"));
-      return;
-    }
+    FocusFirstTabInOpenGroup(saved_group->local_group_id().value());
+    return;
   }
 
   // If our tab group was not found in any tabstrip model, open the group in
   // this browser's tabstrip model.
   TabStripModel* tab_strip_model_for_creation = browser->tab_strip_model();
 
+  // TODO(crbug/1444508): Reduce logic / number of nested data structures to
+  // keep the webcontents and SavedTab Ids paired by using a mapping instead.
+  // Update the listeners to support this change. Then decouple the logic of the
+  // for loop below from this function to make crashes easier to parse.
   std::vector<content::WebContents*> opened_web_contents;
   std::vector<std::pair<content::WebContents*, base::Uuid>>
       local_and_saved_tab_mapping;
@@ -365,6 +350,28 @@
   listener_.RemoveLocalGroupFromSync(removed_group->local_group_id().value());
 }
 
+void SavedTabGroupKeyedService::FocusFirstTabInOpenGroup(
+    tab_groups::TabGroupId local_group_id) {
+  Browser* browser_for_activation =
+      SavedTabGroupUtils::GetBrowserWithTabGroupId(local_group_id);
+
+  // Only activate the tab group's first tab if it exists in any browser's
+  // tabstrip model.
+  CHECK(browser_for_activation);
+
+  absl::optional<int> first_tab = browser_for_activation->tab_strip_model()
+                                      ->group_model()
+                                      ->GetTabGroup(local_group_id)
+                                      ->GetFirstTab();
+  DCHECK(first_tab.has_value());
+  browser_for_activation->ActivateContents(
+      browser_for_activation->tab_strip_model()->GetWebContentsAt(
+          first_tab.value()));
+
+  base::RecordAction(
+      base::UserMetricsAction("TabGroups_SavedTabGroups_Focused"));
+}
+
 const TabStripModel* SavedTabGroupKeyedService::GetTabStripModelWithTabGroupId(
     const tab_groups::TabGroupId& local_group_id) {
   const Browser* const browser =
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h
index 77612a4..bf71931 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h
@@ -61,6 +61,10 @@
       const absl::optional<base::Uuid>& tab_guid) override;
 
  private:
+  // Activates the first tab in saved group that is already opened when its
+  // button is pressed.
+  void FocusFirstTabInOpenGroup(tab_groups::TabGroupId local_group_id);
+
   // Returns a pointer to the TabStripModel which contains `local_group_id`.
   const TabStripModel* GetTabStripModelWithTabGroupId(
       const tab_groups::TabGroupId& local_group_id);
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service_unittest.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service_unittest.cc
index e97eede..3a361bb 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service_unittest.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service_unittest.cc
@@ -238,6 +238,49 @@
                "");
 }
 
+TEST_F(SavedTabGroupKeyedServiceUnitTest, AlreadyOpenedGroupIsFocused) {
+  Browser* browser_1 = AddBrowser();
+  ASSERT_EQ(0, browser_1->tab_strip_model()->count());
+
+  // Add 2 tabs to the browser.
+  AddTabToBrowser(browser_1, 0);
+  AddTabToBrowser(browser_1, 0);
+  ASSERT_EQ(2, browser_1->tab_strip_model()->count());
+
+  const tab_groups::TabGroupId tab_group_id_1 =
+      browser_1->tab_strip_model()->AddToNewGroup({0});
+
+  const base::Uuid guid_1 = base::Uuid::GenerateRandomV4();
+
+  // Store the guid to tab_group_id association in the keyed service. We should
+  // expect at the end of the test, `tab_group_id_3` has no association with the
+  // SavedTabGroupModel at all.
+  service()->StoreLocalToSavedId(guid_1, tab_group_id_1);
+
+  // Populate the SavedTabGroupModel with some test data to simulate the browser
+  // loading in persisted data on startup.
+  std::vector<SavedTabGroupTab> group_1_tabs = {
+      SavedTabGroupTab(GURL("chrome://newtab"), u"New Tab", guid_1)};
+
+  SavedTabGroup saved_group_1(u"Group 1", tab_groups::TabGroupColorId::kGrey,
+                              std::move(group_1_tabs), guid_1);
+
+  service()->model()->Add(saved_group_1);
+
+  // Notify the KeyedService that the SavedTabGroupModel has loaded all local
+  // data triggered by the completion of SavedTabGroupModel::LoadStoredEntries.
+  service()->model()->LoadStoredEntries({});
+
+  // Activate the second tab.
+  browser_1->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_EQ(1, browser_1->tab_strip_model()->active_index());
+
+  service()->OpenSavedTabGroupInBrowser(browser_1, guid_1);
+
+  // Ensure the first tab in the saved group is activated.
+  EXPECT_EQ(0, browser_1->tab_strip_model()->active_index());
+}
+
 TEST_F(SavedTabGroupKeyedServiceUnitTest,
        RestoredGroupWithoutSavedGuidIsDiscarded) {
   Browser* browser_1 = AddBrowser();
diff --git a/chrome/browser/ui/test/popup_browsertest.cc b/chrome/browser/ui/test/popup_browsertest.cc
index 713ed872..8d36857 100644
--- a/chrome/browser/ui/test/popup_browsertest.cc
+++ b/chrome/browser/ui/test/popup_browsertest.cc
@@ -13,12 +13,58 @@
 #include "content/public/test/browser_test_utils.h"
 #include "ui/display/display.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
 
 namespace {
 
 // Tests of window placement for popup browser windows.
 using PopupTest = PopupTestBase;
 
+// A helper class to wait for the bounds of two widgets to become equal.
+class WidgetBoundsEqualWaiter final : public views::WidgetObserver {
+ public:
+  WidgetBoundsEqualWaiter(views::Widget* widget, views::Widget* widget_cmp)
+      : widget_(widget), widget_cmp_(widget_cmp) {
+    widget_->AddObserver(this);
+    widget_cmp_->AddObserver(this);
+  }
+
+  WidgetBoundsEqualWaiter(const WidgetBoundsEqualWaiter&) = delete;
+  WidgetBoundsEqualWaiter& operator=(const WidgetBoundsEqualWaiter&) = delete;
+  ~WidgetBoundsEqualWaiter() final {
+    widget_->RemoveObserver(this);
+    widget_cmp_->RemoveObserver(this);
+  }
+
+  // views::WidgetObserver:
+  void OnWidgetBoundsChanged(views::Widget* widget,
+                             const gfx::Rect& rect) final {
+    if (WidgetsBoundsEqual()) {
+      widget_->RemoveObserver(this);
+      widget_cmp_->RemoveObserver(this);
+      run_loop_.Quit();
+    }
+  }
+
+  // Wait for changes to occur, or return immediately if they already have.
+  void Wait() {
+    if (!WidgetsBoundsEqual()) {
+      run_loop_.Run();
+    }
+  }
+
+ private:
+  bool WidgetsBoundsEqual() {
+    return widget_->GetWindowBoundsInScreen() ==
+           widget_cmp_->GetWindowBoundsInScreen();
+  }
+  const raw_ptr<views::Widget> widget_ = nullptr;
+  const raw_ptr<views::Widget> widget_cmp_ = nullptr;
+  base::RunLoop run_loop_;
+};
+
 // Ensure `left=0,top=0` popup window feature coordinates are respected.
 IN_PROC_BROWSER_TEST_F(PopupTest, OpenLeftAndTopZeroCoordinates) {
   // Attempt to open a popup at (0,0). Its bounds should match the request, but
@@ -144,6 +190,12 @@
   Browser* opener_popup =
       OpenPopup(browser(), "open('.', '', `" + std::string(kFeatures) + "`)");
 
+  WidgetBoundsEqualWaiter(views::Widget::GetWidgetForNativeWindow(
+                              noopener_popup->window()->GetNativeWindow()),
+                          views::Widget::GetWidgetForNativeWindow(
+                              opener_popup->window()->GetNativeWindow()))
+      .Wait();
+
   EXPECT_EQ(noopener_popup->window()->GetBounds().ToString(),
             opener_popup->window()->GetBounds().ToString());
 }
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 28aedc4..e997bec1 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -1052,7 +1052,7 @@
       !base::FeatureList::IsEnabled(
           password_manager::features::kPasswordManagerRedesign)) {
     sub_menus_.push_back(std::make_unique<AutofillSubMenuModel>(this));
-    AddSubMenuWithStringId(IDC_AUTOFILL_MENU, IDS_AUTOFILL_MENU,
+    AddSubMenuWithStringId(IDC_AUTOFILL_MENU, IDS_PASSWORDS_AND_AUTOFILL_MENU,
                            sub_menus_.back().get());
   }
 
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h
index b737415..93b79199 100644
--- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_OFFER_NOTIFICATION_BUBBLE_VIEWS_H_
 #define CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_OFFER_NOTIFICATION_BUBBLE_VIEWS_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/autofill/autofill_bubble_base.h"
 #include "chrome/browser/ui/autofill/payments/offer_notification_bubble_controller.h"
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
index 85bc2f96..80d8b08 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
@@ -23,6 +23,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/saved_tab_groups/saved_tab_group_tab.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
@@ -74,6 +75,7 @@
  public:
   explicit OverflowMenu(SavedTabGroupBar& parent_bar)
       : parent_bar_(parent_bar) {}
+
   ~OverflowMenu() override = default;
 
   bool GetDropFormats(
@@ -187,9 +189,7 @@
       animations_enabled_(animations_enabled) {
   SetAccessibilityProperties(
       ax::mojom::Role::kToolbar,
-      /*name=*/l10n_util::GetStringUTF16(IDS_ACCNAME_SAVED_TAB_GROUPS),
-      /*description=*/absl::nullopt,
-      /*role_description=*/absl::nullopt);
+      /*name=*/l10n_util::GetStringUTF16(IDS_ACCNAME_SAVED_TAB_GROUPS));
 
   SetProperty(views::kElementIdentifierKey, kSavedTabGroupBarElementId);
 
@@ -211,8 +211,8 @@
   overflow_button_ = AddChildView(
       std::make_unique<SavedTabGroupOverflowButton>(base::BindRepeating(
           &SavedTabGroupBar::MaybeShowOverflowMenu, base::Unretained(this))));
-  HideOverflowButton();
 
+  HideOverflowButton();
   LoadAllButtonsFromModel();
   ReorderChildView(overflow_button_, children().size());
 }
@@ -603,16 +603,19 @@
 void SavedTabGroupBar::OnTabGroupButtonPressed(const base::Uuid& id,
                                                const ui::Event& event) {
   DCHECK(saved_tab_group_model_ && saved_tab_group_model_->Contains(id));
-
   const SavedTabGroup* group = saved_tab_group_model_->Get(id);
 
-  // TODO: Handle click if group has already been opened (crbug.com/1238539)
-  // left click on a saved tab group opens all links in new group
-  if (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) {
-    if (group->saved_tabs().empty()) {
-      return;
-    }
-    SavedTabGroupKeyedService* keyed_service =
+  if (group->saved_tabs().empty()) {
+    return;
+  }
+
+  bool space_pressed = event.IsKeyEvent() && event.AsKeyEvent()->key_code() ==
+                                                 ui::KeyboardCode::VKEY_SPACE;
+
+  bool left_mouse_button_pressed = event.flags() & ui::EF_LEFT_MOUSE_BUTTON;
+
+  if (left_mouse_button_pressed || space_pressed) {
+    SavedTabGroupKeyedService* const keyed_service =
         SavedTabGroupServiceFactory::GetForProfile(browser_->profile());
 
     keyed_service->OpenSavedTabGroupInBrowser(browser_, group->saved_guid());
@@ -630,29 +633,10 @@
     return;
   }
 
-  auto bubble_delegate = std::make_unique<views::BubbleDialogDelegate>(
-      overflow_button_, views::BubbleBorder::TOP_LEFT);
+  // 1. Build the vertical list of buttons in the over flow menu.
+  auto overflow_menu = std::make_unique<OverflowMenu>(*this);
 
-  bubble_delegate_ = bubble_delegate.get();
-  bubble_delegate_->SetShowTitle(false);
-  bubble_delegate_->SetShowCloseButton(false);
-  bubble_delegate_->SetButtons(ui::DIALOG_BUTTON_NONE);
-  bubble_delegate_->set_margins(gfx::Insets());
-  bubble_delegate_->set_fixed_width(200);
-  bubble_delegate_->set_adjust_if_offscreen(true);
-  bubble_delegate_->set_close_on_deactivate(true);
-
-  overflow_menu_ =
-      bubble_delegate_->SetContentsView(std::make_unique<OverflowMenu>(*this));
-
-  const gfx::Insets insets = gfx::Insets::TLBR(16, 16, 16, 48);
-  auto box = std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical, insets,
-      kOverflowMenuButtonPadding);
-  box->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kStart);
-  overflow_menu_->SetLayoutManager(std::move(box));
-
-  // Add all buttons that are not currently visible to the overflow menu
+  // Add all buttons that are not currently visible to the overflow menu.
   for (const auto* const child : children()) {
     if (child->GetVisible() ||
         !views::IsViewClass<SavedTabGroupButton>(child)) {
@@ -664,7 +648,7 @@
     const SavedTabGroup* const group =
         saved_tab_group_model_->Get(button->guid());
 
-    overflow_menu_->AddChildView(std::make_unique<SavedTabGroupButton>(
+    overflow_menu->AddChildView(std::make_unique<SavedTabGroupButton>(
         *group,
         base::BindRepeating(&SavedTabGroupBar::page_navigator,
                             base::Unretained(this)),
@@ -673,13 +657,37 @@
         browser_, animations_enabled_));
   }
 
+  // Make the list of buttons vertical.
+  const gfx::Insets insets = gfx::Insets::TLBR(16, 16, 16, 48);
+  auto box = std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical, insets,
+      kOverflowMenuButtonPadding);
+  box->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kStart);
+  overflow_menu->SetLayoutManager(std::move(box));
+
+  // 2. Create the bubble / background which will hold the overflow menu.
+  // TODO(dljames): Set the background color to match the current theme.
+  auto bubble_delegate = std::make_unique<views::BubbleDialogDelegate>(
+      overflow_button_, views::BubbleBorder::TOP_LEFT);
+  bubble_delegate->set_fixed_width(200);
+  bubble_delegate->set_margins(gfx::Insets());
+  bubble_delegate->set_adjust_if_offscreen(true);
+  bubble_delegate->set_close_on_deactivate(true);
+  bubble_delegate->SetShowTitle(false);
+  bubble_delegate->SetButtons(ui::DIALOG_BUTTON_NONE);
+  bubble_delegate->SetShowCloseButton(false);
+  bubble_delegate->SetEnableArrowKeyTraversal(true);
+  bubble_delegate->SetContentsView(std::move(overflow_menu));
+
+  bubble_delegate_ = bubble_delegate.get();
+  overflow_menu_ =
+      views::AsViewClass<OverflowMenu>(bubble_delegate->GetContentsView());
+
+  // 3. Display the menu.
   auto* const widget =
       views::BubbleDialogDelegate::CreateBubble(std::move(bubble_delegate));
-
   widget_observation_.Observe(widget);
-
   widget->Show();
-  return;
 }
 
 void SavedTabGroupBar::HideOverflowMenu() {
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
index b9d9165..2e6d082 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
@@ -101,7 +101,7 @@
               base::Unretained(this)),
           views::MenuRunner::CONTEXT_MENU | views::MenuRunner::IS_NESTED) {
   SetAccessibilityProperties(
-      ax::mojom::Role::kButton, /*name=*/absl::nullopt,
+      ax::mojom::Role::kButton, /*name=*/GetAccessibleNameForButton(),
       /*description=*/absl::nullopt,
       l10n_util::GetStringUTF16(
           IDS_ACCNAME_SAVED_TAB_GROUP_BUTTON_ROLE_DESCRIPTION));
@@ -124,6 +124,7 @@
       ChromeDistanceMetric::DISTANCE_RELATED_LABEL_HORIZONTAL_LIST));
   views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(0),
                                                 kButtonRadius);
+  SetFocusBehavior(FocusBehavior::ALWAYS);
 
   set_drag_controller(this);
 }
@@ -148,6 +149,21 @@
              : std::u16string();
 }
 
+bool SavedTabGroupButton::OnKeyPressed(const ui::KeyEvent& event) {
+  if (event.key_code() == ui::KeyboardCode::VKEY_RETURN) {
+    ShowContextMenu(GetKeyboardContextMenuLocation(),
+                    ui::MenuSourceType::MENU_SOURCE_KEYBOARD);
+    return true;
+  } else if (event.key_code() == ui::KeyboardCode::VKEY_SPACE) {
+    NotifyClick(event);
+    return true;
+  }
+
+  return false;
+}
+
+void SavedTabGroupButton::OnFocus() {}
+
 void SavedTabGroupButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   views::MenuButton::GetAccessibleNodeData(node_data);
   node_data->SetNameChecked(GetAccessibleNameForButton());
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
index 9611dd7..638eb42 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
@@ -28,10 +28,7 @@
 class Canvas;
 }
 
-// The display button for the Saved Tab Group in the bookmarks bar.
-// Note: we currently recreate this button if any content (title, tabs, color,
-// etc.) changes
-// TODO(dljames): Find a way to not recreate the button for each update.
+// The visual representation of a SavedTabGroup shown in the bookmarks bar.
 class SavedTabGroupButton : public views::MenuButton,
                             public views::DragController {
  public:
@@ -55,6 +52,10 @@
       const override;
   void OnThemeChanged() override;
 
+  // views::View
+  bool OnKeyPressed(const ui::KeyEvent& event) override;
+  void OnFocus() override;
+
   // views::DragController
   void WriteDragDataForView(View* sender,
                             const gfx::Point& press_pt,
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc
index 7b1fca1..71bbd0e9 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_overflow_button.cc
@@ -23,30 +23,24 @@
 
 SavedTabGroupOverflowButton::SavedTabGroupOverflowButton(
     PressedCallback callback)
-    : views::MenuButton(std::move(callback), u"") {
-  SetAccessibleName(
+    : views::MenuButton(std::move(callback)) {
+  SetAccessibilityProperties(
+      ax::mojom::Role::kMenu,
       l10n_util::GetStringUTF16(IDS_ACCNAME_SAVED_TAB_GROUPS_CHEVRON));
   SetTooltipText(
       l10n_util::GetStringUTF16(IDS_SAVED_TAB_GROUPS_OVERFLOW_BUTTON_TOOLTIP));
   ConfigureInkDropForToolbar(this);
   SetImageLabelSpacing(ChromeLayoutProvider::Get()->GetDistanceMetric(
       DISTANCE_RELATED_LABEL_HORIZONTAL_LIST));
-  views::InstallPillHighlightPathGenerator(this);
 }
 
 SavedTabGroupOverflowButton::~SavedTabGroupOverflowButton() = default;
 
 void SavedTabGroupOverflowButton::GetAccessibleNodeData(
     ui::AXNodeData* node_data) {
-  // If the button would have no name, avoid crashing by setting the name
-  // explicitly empty.
-  if (GetAccessibleName().empty()) {
-    node_data->SetNameExplicitlyEmpty();
-  }
-
   views::MenuButton::GetAccessibleNodeData(node_data);
-  node_data->AddStringAttribute(
-      ax::mojom::StringAttribute::kRoleDescription,
+  node_data->role = ax::mojom::Role::kMenu;
+  node_data->SetNameChecked(
       l10n_util::GetStringUTF8(IDS_ACCNAME_SAVED_TAB_GROUPS_CHEVRON));
 }
 
diff --git a/chrome/browser/ui/views/borealis/borealis_installer_view.cc b/chrome/browser/ui/views/borealis/borealis_installer_view.cc
index e255820..6e43fb19 100644
--- a/chrome/browser/ui/views/borealis/borealis_installer_view.cc
+++ b/chrome/browser/ui/views/borealis/borealis_installer_view.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "ash/public/cpp/ash_typography.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/style/color_mode_observer.h"
 #include "ash/public/cpp/style/dark_light_mode_controller.h"
@@ -14,6 +15,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
 #include "chrome/browser/ash/borealis/borealis_app_launcher.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager.h"
 #include "chrome/browser/ash/borealis/borealis_features.h"
@@ -35,6 +37,7 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/window.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -47,6 +50,7 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/progress_bar.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/style/typography.h"
 #include "ui/views/view_class_properties.h"
 
 namespace {
@@ -54,8 +58,6 @@
 BorealisInstallerView* g_borealis_installer_view = nullptr;
 
 constexpr auto kButtonRowInsets = gfx::Insets::TLBR(0, 64, 32, 64);
-constexpr int kWindowWidth = 768;
-constexpr int kWindowHeight = 636;
 
 void ShowBorealisInstallerViewIfAllowed(
     Profile* profile,
@@ -78,6 +80,23 @@
   g_borealis_installer_view->GetWidget()->Show();
 }
 
+// Returns the text to be used for a predicted completion time, which is
+// something like "starting up" or "3 mins remaining" depending on how
+// accurately we can predict.
+std::u16string GetInstallationPredictionText(const base::Time& start_time,
+                                             double completion_proportion) {
+  base::TimeDelta duration = base::Time::Now() - start_time;
+  // We have no confidence in the prediction for the first second or for
+  // too-small proportions.
+  if (completion_proportion < 0.001 || duration < base::Seconds(1)) {
+    return l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_ONGOING_INACTIVE);
+  }
+  // Linear-interpolation to predict remaining time.
+  base::TimeDelta remaining = (duration / completion_proportion) - duration;
+  return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
+                                ui::TimeFormat::LENGTH_SHORT, remaining);
+}
+
 }  // namespace
 
 // Defined in chrome/browser/ash/borealis/borealis_util.h.
@@ -110,91 +129,94 @@
     : app_name_(l10n_util::GetStringUTF16(IDS_BOREALIS_APP_NAME)),
       profile_(profile),
       observation_(this) {
-  // Layout constants from the spec used for the plugin vm installer.
-  constexpr auto kDialogInsets = gfx::Insets::TLBR(60, 64, 0, 64);
-  const int kPrimaryMessageHeight = views::style::GetLineHeight(
-      CONTEXT_HEADLINE, views::style::STYLE_PRIMARY);
-  const int kSecondaryMessageHeight = views::style::GetLineHeight(
-      views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_SECONDARY);
-  const int kInstallationProgressMessageHeight = views::style::GetLineHeight(
-      CONTEXT_DIALOG_BODY_TEXT_SMALL, views::style::STYLE_SECONDARY);
-  constexpr int kProgressBarHeight = 5;
-  constexpr int kProgressBarTopMargin = 32;
-
   SetCanMinimize(true);
   set_draggable(true);
-  // Removed margins so dialog insets specify it instead.
-  set_margins(gfx::Insets());
+  set_margins(gfx::Insets::TLBR(80, 40, 40, 40));
+  set_corner_radius(12);
 
-  views::BoxLayout* layout =
-      SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kVertical, kDialogInsets));
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal));
 
-  views::View* upper_container_view =
+  views::View* left_container_view =
       AddChildView(std::make_unique<views::View>());
-  upper_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical, gfx::Insets()));
-  AddChildView(upper_container_view);
+  views::BoxLayout* left_layout =
+      left_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical, gfx::Insets()));
+  left_layout->set_inside_border_insets(gfx::Insets::TLBR(0, 0, 0, 80));
+  AddChildView(left_container_view);
 
-  views::View* lower_container_view =
-      AddChildView(std::make_unique<views::View>());
-  lower_container_layout_ =
-      lower_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kVertical));
-  AddChildView(lower_container_view);
+  views::ImageView* flair =
+      left_container_view->AddChildView(std::make_unique<views::ImageView>());
+  gfx::ImageSkia* s = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+      IDR_LOGO_BOREALIS_FLAIR);
+  flair->SetHorizontalAlignment(views::ImageView::Alignment::kLeading);
+  flair->SetImageSize({32, 32});
+  flair->SetImage(s);
 
-  primary_message_label_ = new TitleLabel(GetPrimaryMessage(), CONTEXT_HEADLINE,
-                                          views::style::STYLE_PRIMARY);
-  primary_message_label_->SetProperty(
-      views::kMarginsKey, gfx::Insets::TLBR(kPrimaryMessageHeight, 0, 0, 0));
-  primary_message_label_->SetMultiLine(false);
+  primary_message_label_ =
+      new TitleLabel(GetPrimaryMessage(), ash::CONTEXT_HEADLINE_OVERSIZED,
+                     views::style::STYLE_PRIMARY);
+  primary_message_label_->SetProperty(views::kMarginsKey,
+                                      gfx::Insets::TLBR(40, 0, 0, 0));
+  primary_message_label_->SetMultiLine(true);
   primary_message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  upper_container_view->AddChildView(primary_message_label_.get());
+  primary_message_label_->SetMaximumWidth(264);
+  left_container_view->AddChildView(primary_message_label_.get());
 
-  views::View* secondary_message_container_view =
-      AddChildView(std::make_unique<views::View>());
-  secondary_message_container_view->SetLayoutManager(
-      std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kVertical,
-          gfx::Insets::TLBR(kSecondaryMessageHeight, 0, 0, 0)));
-  upper_container_view->AddChildView(secondary_message_container_view);
   secondary_message_label_ = new views::Label(
       GetSecondaryMessage(), views::style::CONTEXT_DIALOG_BODY_TEXT,
       views::style::STYLE_SECONDARY);
+  secondary_message_label_->SetProperty(views::kMarginsKey,
+                                        gfx::Insets::TLBR(16, 0, 0, 0));
   secondary_message_label_->SetMultiLine(true);
   secondary_message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  secondary_message_container_view->AddChildView(
-      secondary_message_label_.get());
+  left_container_view->AddChildView(secondary_message_label_.get());
 
-  progress_bar_ = new views::ProgressBar(kProgressBarHeight);
-  progress_bar_->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(kProgressBarTopMargin - kProgressBarHeight, 0, 0, 0));
-  upper_container_view->AddChildView(progress_bar_.get());
+  views::View* progress_container =
+      left_container_view->AddChildView(std::make_unique<views::View>());
+  progress_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal,
+      gfx::Insets::TLBR(32, 0, 0, 0)));
+  installation_progress_percentage_label_ =
+      progress_container->AddChildView(std::make_unique<views::Label>(
+          std::u16string(), views::style::CONTEXT_DIALOG_BODY_TEXT,
+          views::style::STYLE_SECONDARY));
+  installation_progress_separator_ =
+      progress_container->AddChildView(std::make_unique<views::Label>(
+          std::u16string(), views::style::CONTEXT_DIALOG_BODY_TEXT,
+          views::style::STYLE_SECONDARY));
+  installation_progress_separator_->SetText(u"|");
+  installation_progress_separator_->SetProperty(views::kMarginsKey,
+                                                gfx::Insets::TLBR(0, 8, 0, 8));
+  installation_progress_eta_label_ =
+      progress_container->AddChildView(std::make_unique<views::Label>(
+          std::u16string(), views::style::CONTEXT_DIALOG_BODY_TEXT,
+          views::style::STYLE_SECONDARY));
 
-  installation_progress_message_label_ =
-      new views::Label(std::u16string(), CONTEXT_DIALOG_BODY_TEXT_SMALL,
-                       views::style::STYLE_SECONDARY);
-  installation_progress_message_label_->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(kInstallationProgressMessageHeight, 0, 0, 0));
-  installation_progress_message_label_->SetMultiLine(false);
-  installation_progress_message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  upper_container_view->AddChildView(
-      installation_progress_message_label_.get());
+  progress_bar_ = new views::ProgressBar();
+  progress_bar_->SetProperty(views::kMarginsKey,
+                             gfx::Insets::TLBR(16, 0, 0, 0));
+  left_container_view->AddChildView(progress_bar_.get());
+
+  views::View* right_container_view =
+      AddChildView(std::make_unique<views::View>());
+  right_container_layout_ =
+      right_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical));
+  right_container_layout_->set_inside_border_insets(
+      gfx::Insets::TLBR(64, 0, 64, 0));
+  AddChildView(right_container_view);
 
   big_image_ = new views::ImageView();
-  lower_container_view->AddChildView(big_image_.get());
-
-  // Make sure the lower_container_view is pinned to the bottom of the dialog.
-  lower_container_layout_->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::kEnd);
-  layout->SetFlexForView(lower_container_view, 1, true);
+  big_image_->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
+  big_image_->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
+  right_container_view->AddChildView(big_image_.get());
 
   ash::DarkLightModeController* dark_light_controller =
       ash::DarkLightModeController::Get();
-  if (dark_light_controller)
+  if (dark_light_controller) {
     dark_light_controller->AddObserver(this);
+  }
 }
 
 BorealisInstallerView::~BorealisInstallerView() {
@@ -205,8 +227,9 @@
   }
   ash::DarkLightModeController* dark_light_controller =
       ash::DarkLightModeController::Get();
-  if (dark_light_controller)
+  if (dark_light_controller) {
     dark_light_controller->RemoveObserver(this);
+  }
 
   g_borealis_installer_view = nullptr;
 }
@@ -235,8 +258,9 @@
     borealis::BorealisService::GetForProfile(profile_)->AppLauncher().Launch(
         borealis::kClientAppId,
         base::BindOnce([](borealis::BorealisAppLauncher::LaunchResult result) {
-          if (result == borealis::BorealisAppLauncher::LaunchResult::kSuccess)
+          if (result == borealis::BorealisAppLauncher::LaunchResult::kSuccess) {
             return;
+          }
           LOG(ERROR) << "Failed to launch borealis after install: code="
                      << static_cast<int>(result);
         }));
@@ -254,8 +278,9 @@
         ->ContextManager()
         .ShutDownBorealis(
             base::BindOnce([](borealis::BorealisShutdownResult result) {
-              if (result == borealis::BorealisShutdownResult::kSuccess)
+              if (result == borealis::BorealisShutdownResult::kSuccess) {
                 return;
+              }
               LOG(ERROR) << "Failed to shutdown borealis after install: code="
                          << static_cast<int>(result);
             }));
@@ -273,6 +298,7 @@
 
 void BorealisInstallerView::OnProgressUpdated(double fraction_complete) {
   progress_bar_->SetValue(fraction_complete);
+  SetProgressMessageLabel();
 }
 
 void BorealisInstallerView::OnInstallationEnded(
@@ -293,10 +319,6 @@
   OnStateUpdated();
 }
 
-gfx::Size BorealisInstallerView::CalculatePreferredSize() const {
-  return gfx::Size(kWindowWidth, kWindowHeight);
-}
-
 std::u16string BorealisInstallerView::GetPrimaryMessage() const {
   switch (state_) {
     case State::kConfirmInstall:
@@ -323,21 +345,6 @@
   }
 }
 
-std::u16string BorealisInstallerView::GetProgressMessage() const {
-  if (state_ != State::kInstalling)
-    return {};
-  switch (installing_state_) {
-    case InstallingState::kInactive:
-    case InstallingState::kCheckingIfAllowed:
-      return l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_ONGOING_INACTIVE);
-    case InstallingState::kInstallingDlc:
-      return l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_ONGOING_DLC);
-    case InstallingState::kStartingUp:
-    case InstallingState::kAwaitingApplications:
-      return l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_ONGOING_DRYRUN);
-  }
-}
-
 void BorealisInstallerView::SetInstallingStateForTesting(
     InstallingState new_state) {
   installing_state_ = new_state;
@@ -438,11 +445,25 @@
 }
 
 void BorealisInstallerView::SetProgressMessageLabel() {
-  std::u16string message = GetProgressMessage();
-  installation_progress_message_label_->SetText(message);
-  installation_progress_message_label_->SetVisible(!message.empty());
-  installation_progress_message_label_->NotifyAccessibilityEvent(
+  bool in_progress = state_ == State::kInstalling;
+  installation_progress_percentage_label_->SetVisible(in_progress);
+  installation_progress_separator_->SetVisible(in_progress);
+  installation_progress_eta_label_->SetVisible(in_progress);
+  if (!in_progress) {
+    return;
+  }
+
+  int percentage = progress_bar_->GetValue() * 100;
+  installation_progress_percentage_label_->SetText(
+      l10n_util::GetStringFUTF16Int(IDS_BOREALIS_INSTALLER_ONGOING_PERCENTAGE,
+                                    percentage));
+  installation_progress_percentage_label_->SetVisible(true);
+  installation_progress_percentage_label_->NotifyAccessibilityEvent(
       ax::mojom::Event::kTextChanged, true);
+
+  installation_progress_eta_label_->SetText(GetInstallationPredictionText(
+      install_start_time_, progress_bar_->GetValue()));
+  installation_progress_eta_label_->SetVisible(true);
 }
 
 void BorealisInstallerView::SetImage() {
@@ -452,8 +473,6 @@
   constexpr int kCompleteBottomInsetDp = 64;
 
   auto set_image = [this](int image_id, int bottom_inset_dp) {
-    lower_container_layout_->set_inside_border_insets(
-        gfx::Insets::TLBR(0, 0, bottom_inset_dp, 0));
     gfx::ImageSkia* s =
         ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(image_id);
     // The image assets are sized so that we can display them at half their
@@ -484,11 +503,14 @@
 
   borealis::BorealisInstaller& installer =
       borealis::BorealisService::GetForProfile(profile_)->Installer();
-  if (observation_.IsObserving())
+  if (observation_.IsObserving()) {
     observation_.Reset();
+  }
   observation_.Observe(&installer);
   installer.Start();
 
+  install_start_time_ = base::Time::Now();
+
   OnStateUpdated();
 }
 
diff --git a/chrome/browser/ui/views/borealis/borealis_installer_view.h b/chrome/browser/ui/views/borealis/borealis_installer_view.h
index f296637..d836e82e 100644
--- a/chrome/browser/ui/views/borealis/borealis_installer_view.h
+++ b/chrome/browser/ui/views/borealis/borealis_installer_view.h
@@ -54,7 +54,6 @@
   bool ShouldShowWindowTitle() const override;
   bool Accept() override;
   bool Cancel() override;
-  gfx::Size CalculatePreferredSize() const override;
 
   // borealis::BorealisInstaller::Observer implementation.
   void OnStateUpdated(
@@ -67,7 +66,6 @@
   // Public for testing purposes.
   std::u16string GetPrimaryMessage() const;
   std::u16string GetSecondaryMessage() const;
-  std::u16string GetProgressMessage() const;
 
   void SetInstallingStateForTesting(InstallingState new_state);
 
@@ -112,11 +110,16 @@
   raw_ptr<views::Label, ExperimentalAsh> primary_message_label_ = nullptr;
   raw_ptr<views::Label, ExperimentalAsh> secondary_message_label_ = nullptr;
   raw_ptr<views::ProgressBar, ExperimentalAsh> progress_bar_ = nullptr;
-  raw_ptr<views::Label, ExperimentalAsh> installation_progress_message_label_ =
+  raw_ptr<views::Label, ExperimentalAsh>
+      installation_progress_percentage_label_ = nullptr;
+  raw_ptr<views::Label, ExperimentalAsh> installation_progress_separator_ =
       nullptr;
-  raw_ptr<views::BoxLayout, ExperimentalAsh> lower_container_layout_ = nullptr;
+  raw_ptr<views::Label, ExperimentalAsh> installation_progress_eta_label_ =
+      nullptr;
+  raw_ptr<views::BoxLayout, ExperimentalAsh> right_container_layout_ = nullptr;
   raw_ptr<views::ImageView, ExperimentalAsh> big_image_ = nullptr;
 
+  base::Time install_start_time_;
   State state_ = State::kConfirmInstall;
   InstallingState installing_state_ = InstallingState::kInactive;
   absl::optional<borealis::BorealisInstallResult> result_;
diff --git a/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc b/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc
index 9d1127e..74765ec3 100644
--- a/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc
+++ b/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc
@@ -99,7 +99,6 @@
     EXPECT_EQ(
         view_->GetPrimaryMessage(),
         l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_CONFIRMATION_TITLE));
-    EXPECT_EQ(view_->GetProgressMessage(), u"");
   }
 
   void ExpectInstallationInProgress() {
@@ -110,7 +109,6 @@
     EXPECT_EQ(
         view_->GetSecondaryMessage(),
         l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_ONGOING_MESSAGE));
-    EXPECT_NE(view_->GetProgressMessage(), u"");
   }
 
   void ExpectInstallationFailed() {
@@ -127,7 +125,6 @@
               l10n_util::GetStringUTF16(IDS_APP_CLOSE));
     EXPECT_EQ(view_->GetPrimaryMessage(),
               l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_FINISHED_TITLE));
-    EXPECT_EQ(view_->GetProgressMessage(), u"");
   }
 
   void AcceptInstallation() {
diff --git a/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc b/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc
index c45f063b..b5f93e6 100644
--- a/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc
+++ b/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc
@@ -4,16 +4,20 @@
 
 #include "chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.h"
 
+#include "base/command_line.h"
 #include "chrome/browser/media/webrtc/desktop_media_list.h"
 #include "chrome/browser/media/webrtc/desktop_media_picker_manager.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/extensions/extensions_container.h"
+#include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/desktop_capture/share_this_tab_source_view.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/constrained_window/constrained_window_views.h"
+#include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/desktop_media_id.h"
@@ -22,6 +26,8 @@
 #include "media/base/media_switches.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/widget/widget.h"
@@ -30,6 +36,13 @@
 #include "ui/aura/window_tree_host.h"
 #endif
 
+namespace {
+
+constexpr gfx::Insets kAudioToggleInsets = gfx::Insets::VH(8, 16);
+constexpr int kAudioToggleChildSpacing = 8;
+
+}  // namespace
+
 ShareThisTabDialogView::ShareThisTabDialogView(
     const DesktopMediaPicker::Params& params,
     ShareThisTabDialogViews* parent)
@@ -54,24 +67,17 @@
           views::DialogContentType::kText, views::DialogContentType::kControl),
       provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_VERTICAL_SMALL)));
 
-  auto description_label = std::make_unique<views::Label>();
-  description_label->SetMultiLine(true);
-  description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  description_label_ = AddChildView(std::move(description_label));
+  description_label_ = AddChildView(std::make_unique<views::Label>());
+  description_label_->SetMultiLine(true);
+  description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   description_label_->SetText(
       l10n_util::GetStringUTF16(IDS_SHARE_THIS_TAB_DIALOG_TEXT));
 
-  View* source_container = AddChildView(std::make_unique<views::View>());
-  views::BoxLayout* source_layout =
-      source_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kHorizontal));
-  source_layout->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::kCenter);
-  source_layout->set_cross_axis_alignment(
-      views::BoxLayout::CrossAxisAlignment::kCenter);
+  SetupSourceView();
 
-  source_view_ = source_container->AddChildView(
-      std::make_unique<ShareThisTabSourceView>(web_contents_));
+  if (params.request_audio) {
+    SetupAudioToggle();
+  }
 
   activation_timer_.Start(
       FROM_HERE,
@@ -135,12 +141,15 @@
 
   source_view_->StopRefreshing();
   if (parent_ && web_contents_) {
-    parent_->NotifyDialogResult(content::DesktopMediaID(
+    content::DesktopMediaID desktop_media_id(
         content::DesktopMediaID::TYPE_WEB_CONTENTS,
         content::DesktopMediaID::kNullId,
         content::WebContentsMediaCaptureId(
             web_contents_->GetPrimaryMainFrame()->GetProcess()->GetID(),
-            web_contents_->GetPrimaryMainFrame()->GetRoutingID())));
+            web_contents_->GetPrimaryMainFrame()->GetRoutingID()));
+    desktop_media_id.audio_share =
+        audio_toggle_button_ && audio_toggle_button_->GetIsOn();
+    parent_->NotifyDialogResult(desktop_media_id);
   }
 
   // Return true to close the window.
@@ -159,6 +168,56 @@
   return false;
 }
 
+void ShareThisTabDialogView::SetupSourceView() {
+  View* source_container = AddChildView(std::make_unique<views::View>());
+  views::BoxLayout* source_layout =
+      source_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kHorizontal));
+  source_layout->set_main_axis_alignment(
+      views::BoxLayout::MainAxisAlignment::kCenter);
+  source_layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kCenter);
+  source_view_ = source_container->AddChildView(
+      std::make_unique<ShareThisTabSourceView>(web_contents_));
+}
+
+void ShareThisTabDialogView::SetupAudioToggle() {
+  View* audio_toggle_container = AddChildView(std::make_unique<views::View>());
+  // TODO(crbug.com/1444707): Create a color_id for this usage.
+  audio_toggle_container->SetBackground(
+      views::CreateSolidBackground(gfx::kGoogleGrey050));
+
+  views::ImageView* audio_icon_view = audio_toggle_container->AddChildView(
+      std::make_unique<views::ImageView>());
+  audio_icon_view->SetImage(ui::ImageModel::FromVectorIcon(
+      vector_icons::kVolumeUpIcon, ui::kColorIcon,
+      GetLayoutConstant(PAGE_INFO_ICON_SIZE)));
+
+  views::Label* audio_toggle_label =
+      audio_toggle_container->AddChildView(std::make_unique<views::Label>());
+  audio_toggle_label->SetText(
+      l10n_util::GetStringUTF16(IDS_SHARE_THIS_TAB_AUDIO_SHARE));
+  audio_toggle_label->SetHorizontalAlignment(
+      gfx::HorizontalAlignment::ALIGN_LEFT);
+
+  audio_toggle_button_ = audio_toggle_container->AddChildView(
+      std::make_unique<views::ToggleButton>());
+  audio_toggle_button_->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_SHARE_THIS_TAB_AUDIO_SHARE));
+  audio_toggle_button_->SetIsOn(
+      !base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kScreenCaptureAudioDefaultUnchecked));
+
+  views::BoxLayout* audio_toggle_layout =
+      audio_toggle_container->SetLayoutManager(
+          std::make_unique<views::BoxLayout>(
+              views::BoxLayout::Orientation::kHorizontal, kAudioToggleInsets));
+  audio_toggle_layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kCenter);
+  audio_toggle_layout->set_between_child_spacing(kAudioToggleChildSpacing);
+  audio_toggle_layout->SetFlexForView(audio_toggle_label, 1);
+}
+
 void ShareThisTabDialogView::Activate() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   source_view_->Activate();
diff --git a/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.h b/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.h
index 5f2b8d2..766db3d4 100644
--- a/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.h
+++ b/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/views/desktop_capture/share_this_tab_source_view.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/button/toggle_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/window/dialog_delegate.h"
 
@@ -39,6 +40,9 @@
   bool ShouldShowCloseButton() const override;
 
  private:
+  void SetupSourceView();
+  void SetupAudioToggle();
+
   void Activate();
 
   const base::WeakPtr<content::WebContents> web_contents_;
@@ -52,6 +56,9 @@
   // or a throbber while the dialog is not yet activated.
   raw_ptr<ShareThisTabSourceView> source_view_ = nullptr;
 
+  raw_ptr<views::ToggleButton, DanglingUntriaged> audio_toggle_button_ =
+      nullptr;
+
   // Timer for an initial delay during which the allow-button is disabled.
   base::OneShotTimer activation_timer_;
   base::WeakPtrFactory<ShareThisTabDialogView> weak_factory_{this};
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h
index 7439bd6..a995f28 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_SECURITY_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_SECURITY_VIEW_H_
 
+#include "base/gtest_prod_util.h"
 #include "chrome/browser/download/download_ui_model.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/metadata/metadata_header_macros.h"
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
index a1d3a3c..6c42dec 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
@@ -129,8 +129,8 @@
 
 ExtensionsMenuMainPageView::ExtensionsMenuMainPageView(
     Browser* browser,
-    ExtensionsMenuHandler* navigation_handler)
-    : browser_(browser), navigation_handler_(navigation_handler) {
+    ExtensionsMenuHandler* menu_handler)
+    : browser_(browser), menu_handler_(menu_handler) {
   // This is set so that the extensions menu doesn't fall outside the monitor in
   // a maximized window in 1024x768. See https://crbug.com/1096630.
   // TODO(crbug.com/1413883): Consider making the height dynamic.
@@ -221,7 +221,7 @@
                       views::BubbleFrameView::CreateCloseButton(
                           base::BindRepeating(
                               &ExtensionsMenuHandler::CloseBubble,
-                              base::Unretained(navigation_handler_))))),
+                              base::Unretained(menu_handler_))))),
           // Contents.
           views::Builder<views::Separator>(),
           views::Builder<views::ScrollView>()
@@ -260,7 +260,7 @@
       // access when toggling the site access toggle.
       base::RepeatingClosure(base::NullCallback()),
       base::BindRepeating(&ExtensionsMenuHandler::OpenSitePermissionsPage,
-                          base::Unretained(navigation_handler_), extension_id));
+                          base::Unretained(menu_handler_), extension_id));
   item->Update(site_access_toggle_state, site_permissions_button_state,
                site_permissions_button_access);
   menu_items_->AddChildViewAt(std::move(item), index);
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
index 201bc57..472ded5 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
@@ -32,9 +32,8 @@
  public:
   METADATA_HEADER(ExtensionsMenuMainPageView);
 
-  explicit ExtensionsMenuMainPageView(
-      Browser* browser,
-      ExtensionsMenuHandler* navigation_handler);
+  explicit ExtensionsMenuMainPageView(Browser* browser,
+                                      ExtensionsMenuHandler* menu_handler);
   ~ExtensionsMenuMainPageView() override = default;
   ExtensionsMenuMainPageView(const ExtensionsMenuMainPageView&) = delete;
   const ExtensionsMenuMainPageView& operator=(
@@ -76,7 +75,7 @@
   content::WebContents* GetActiveWebContents() const;
 
   const raw_ptr<Browser> browser_;
-  const raw_ptr<ExtensionsMenuHandler> navigation_handler_;
+  const raw_ptr<ExtensionsMenuHandler> menu_handler_;
 
   // Subheader section.
   raw_ptr<views::Label> subheader_subtitle_;
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc
index 8d21dd80..441e7b9 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc
@@ -132,7 +132,7 @@
 ExtensionsMenuSitePermissionsPageView::ExtensionsMenuSitePermissionsPageView(
     Browser* browser,
     extensions::ExtensionId extension_id,
-    ExtensionsMenuHandler* navigation_handler)
+    ExtensionsMenuHandler* menu_handler)
     : browser_(browser), extension_id_(extension_id) {
   // TODO(crbug.com/1390952): Same stretch specification as
   // ExtensionsMenuMainPageView. Move to a shared file.
@@ -159,7 +159,7 @@
                     .SetImageLabelSpacing(icon_label_spacing)
                     .SetCallback(base::BindRepeating(
                         &ExtensionsMenuHandler::OnSiteAccessSelected,
-                        base::Unretained(navigation_handler), extension_id,
+                        base::Unretained(menu_handler), extension_id,
                         site_access)),
                 views::Builder<views::Label>()
                     .SetText(GetSiteAccessRadioButtonDescription(site_access))
@@ -186,7 +186,7 @@
                       views::CreateVectorImageButtonWithNativeTheme(
                           base::BindRepeating(
                               &ExtensionsMenuHandler::OpenMainPage,
-                              base::Unretained(navigation_handler)),
+                              base::Unretained(menu_handler)),
                           vector_icons::kArrowBackIcon))
                       .SetTooltipText(
                           l10n_util::GetStringUTF16(IDS_ACCNAME_BACK))
@@ -213,7 +213,7 @@
                       views::BubbleFrameView::CreateCloseButton(
                           base::BindRepeating(
                               &ExtensionsMenuHandler::CloseBubble,
-                              base::Unretained(navigation_handler))))),
+                              base::Unretained(menu_handler))))),
           // Content.
           views::Builder<views::BoxLayoutView>()
               .SetOrientation(views::BoxLayout::Orientation::kVertical)
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h
index 1b11e386..6993f29 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h
@@ -31,7 +31,7 @@
   explicit ExtensionsMenuSitePermissionsPageView(
       Browser* browser,
       extensions::ExtensionId extension_id,
-      ExtensionsMenuHandler* navigation_handler);
+      ExtensionsMenuHandler* menu_handler);
   ExtensionsMenuSitePermissionsPageView(
       const ExtensionsMenuSitePermissionsPageView&) = delete;
   const ExtensionsMenuSitePermissionsPageView& operator=(
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 19a94bd..583a0d6 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
@@ -1084,6 +1084,12 @@
     return false;
   }
 
+  // Enabling immersive mode controller would allow for the user to exit
+  // fullscreen. We don't want this for locked fullscreen windows.
+  if (!CanUserExitFullscreen()) {
+    return false;
+  }
+
   if (chromeos::TabletState::Get()->InTabletMode()) {
     // Tabbed browsers do not support immersive mode in tablet mode. We use the
     // web ui touchable tabstrip, which has its own sliding mechanism to view
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 dbe4ebd..dfc6162 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
@@ -66,6 +66,7 @@
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
 #include "chrome/browser/ui/ash/multi_user/test_multi_user_window_manager.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
+#include "chrome/browser/ui/ash/window_pin_util.h"
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
@@ -1643,6 +1644,47 @@
   EXPECT_FALSE(immersive_controller->IsEnabled());
 }
 
+namespace {
+
+class LockedFullscreenBrowserNonClientFrameViewChromeOSTest
+    : public TopChromeMdParamTest<InProcessBrowserTest> {
+ public:
+  LockedFullscreenBrowserNonClientFrameViewChromeOSTest() = default;
+  LockedFullscreenBrowserNonClientFrameViewChromeOSTest(
+      const LockedFullscreenBrowserNonClientFrameViewChromeOSTest&) = delete;
+  LockedFullscreenBrowserNonClientFrameViewChromeOSTest& operator=(
+      const LockedFullscreenBrowserNonClientFrameViewChromeOSTest&) = delete;
+  ~LockedFullscreenBrowserNonClientFrameViewChromeOSTest() override = default;
+};
+
+}  // namespace
+
+IN_PROC_BROWSER_TEST_P(LockedFullscreenBrowserNonClientFrameViewChromeOSTest,
+                       ToggleTabletMode) {
+  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
+  // Set locked fullscreen state.
+  PinWindow(browser_view->GetWidget()->GetNativeWindow(), /*trusted=*/true);
+
+  // We're fullscreen, immersive is disabled in locked fullscreen, and while
+  // we're at it, also make sure that the shelf is hidden.
+  EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
+  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+  EXPECT_FALSE(IsShelfVisible());
+
+  auto* widget = browser_view->GetWidget();
+  auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
+      views::Widget::GetWidgetForNativeView(widget->GetNativeWindow()));
+  EXPECT_FALSE(immersive_controller->IsEnabled());
+
+  // Enter tablet mode.
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+
+  EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
+  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+  EXPECT_FALSE(IsShelfVisible());
+}
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #define INSTANTIATE_TEST_SUITE(name) \
@@ -1658,4 +1700,5 @@
 INSTANTIATE_TEST_SUITE(HomeLauncherBrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(TabSearchFrameCaptionButtonTest);
 INSTANTIATE_TEST_SUITE(KioskBrowserNonClientFrameViewChromeOSTest);
+INSTANTIATE_TEST_SUITE(LockedFullscreenBrowserNonClientFrameViewChromeOSTest);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
index 4c1aa1df..4bf5673 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
@@ -399,8 +399,14 @@
   // TODO(kerenzhu): we need this workaround due to the design of NonClientView,
   // that the frame part is not an independent child view. If it is an
   // independent view, overriding PaintChildren() will not be necessary.
-  if (!browser_view()->immersive_mode_controller()->IsRevealed())
+  //
+  // Tabbed immersive fullscreen paints its own background. In this case we
+  // allow painting of the frame's children, which fixes a flickering bug:
+  // 1400287.
+  if (browser_view()->UsesImmersiveFullscreenTabbedMode() ||
+      !browser_view()->immersive_mode_controller()->IsRevealed()) {
     BrowserNonClientFrameView::PaintChildren(info);
+  }
 }
 
 gfx::Insets BrowserNonClientFrameViewMac::GetCaptionButtonInsets() const {
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm b/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
index b65ce757..896e1d01 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
@@ -317,7 +317,8 @@
 
 void ImmersiveModeControllerMac::OnDidChangeFocus(views::View* focused_before,
                                                   views::View* focused_now) {
-  if (browser_view_->top_container()->Contains(focused_now)) {
+  if (browser_view_->top_container()->Contains(focused_now) ||
+      browser_view_->tab_overlay_view()->Contains(focused_now)) {
     if (!focus_lock_)
       focus_lock_ = GetRevealedLock(ANIMATE_REVEAL_NO);
   } else {
@@ -425,11 +426,16 @@
 
  private:
   int tab_widget_height_ = 0;
+
+  base::ScopedObservation<views::View, views::ViewObserver>
+      tab_container_observation_{this};
 };
 
 void ImmersiveModeTabbedControllerMac::SetEnabled(bool enabled) {
   BrowserView* browser_view = ImmersiveModeControllerMac::browser_view();
   if (enabled) {
+    tab_container_observation_.Observe(browser_view->tab_overlay_view());
+
     tab_widget_height_ = browser_view->tab_strip_region_view()->height();
     tab_widget_height_ += static_cast<BrowserNonClientFrameViewMac*>(
                               browser_view->frame()->GetFrameView())
@@ -471,6 +477,7 @@
     SetTabNativeWidgetID(tab_overlay_host->bridged_native_widget_id());
     ImmersiveModeControllerMac::SetEnabled(enabled);
   } else {
+    tab_container_observation_.Reset();
     browser_view->tab_overlay_widget()->Hide();
     browser_view->tab_strip_region_view()->SetBorder(nullptr);
     browser_view->top_container()->AddChildViewAt(
diff --git a/chrome/browser/ui/views/lens/lens_side_panel_helper.cc b/chrome/browser/ui/views/lens/lens_side_panel_helper.cc
index 3cf1d68..aac4878 100644
--- a/chrome/browser/ui/views/lens/lens_side_panel_helper.cc
+++ b/chrome/browser/ui/views/lens/lens_side_panel_helper.cc
@@ -45,12 +45,14 @@
 GURL CreateURLForNewTab(const GURL& original_url) {
   if (!IsValidLensResultUrl(original_url))
     return GURL();
+  // Set the side panel max size to zero, as this is not a side panel request.
+  gfx::Size side_panel_initial_size = gfx::Size();
 
   // Append or replace query parameters related to entry point.
   return AppendOrReplaceQueryParametersForLensRequest(
       original_url, EntryPoint::CHROME_OPEN_NEW_TAB_SIDE_PANEL,
       RenderingEnvironment::ONELENS_DESKTOP_WEB_FULLSCREEN,
-      /*is_side_panel_request=*/false);
+      /*is_side_panel_request=*/false, side_panel_initial_size);
 }
 
 void OpenLensSidePanel(Browser* browser,
diff --git a/chrome/browser/ui/views/media_router/cast_dialog_access_code_cast_button.h b/chrome/browser/ui/views/media_router/cast_dialog_access_code_cast_button.h
index 788bf6e..d6b0a6a0 100644
--- a/chrome/browser/ui/views/media_router/cast_dialog_access_code_cast_button.h
+++ b/chrome/browser/ui/views/media_router/cast_dialog_access_code_cast_button.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_MEDIA_ROUTER_CAST_DIALOG_ACCESS_CODE_CAST_BUTTON_H_
 #define CHROME_BROWSER_UI_VIEWS_MEDIA_ROUTER_CAST_DIALOG_ACCESS_CODE_CAST_BUTTON_H_
 
+#include "base/gtest_prod_util.h"
 #include "chrome/browser/ui/views/controls/hover_button.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
diff --git a/chrome/browser/ui/views/media_router/web_contents_display_observer_view.cc b/chrome/browser/ui/views/media_router/web_contents_display_observer_view.cc
index 8a85895..9010a9e 100644
--- a/chrome/browser/ui/views/media_router/web_contents_display_observer_view.cc
+++ b/chrome/browser/ui/views/media_router/web_contents_display_observer_view.cc
@@ -42,7 +42,7 @@
   if (widget_)
     widget_->RemoveObserver(this);
   BrowserList::RemoveObserver(this);
-  CHECK(!IsInObserverList());
+  CHECK(!WidgetObserver::IsInObserverList());
 }
 
 void WebContentsDisplayObserverView::OnBrowserSetLastActive(Browser* browser) {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_view_views_browsertest.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_view_views_browsertest.cc
index 630d4b7c..119ce10 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_view_views_browsertest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_view_views_browsertest.cc
@@ -34,6 +34,7 @@
 #include "content/public/test/test_utils.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/base/theme_provider.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/events/test/event_generator.h"
@@ -195,13 +196,25 @@
   }
 
   // Same in the non-incognito browser.
-  EXPECT_EQ(selection_color_light, GetSelectedColor(browser()));
+  if (features::IsChromeRefresh2023()) {
+    // TODO(khalidpeer): Delete this clause once CR23 colors are supported on
+    //   themed clients. Currently themed clients fall back to pre-CR23 colors.
+    EXPECT_NE(selection_color_light, GetSelectedColor(browser()));
+  } else {
+    EXPECT_EQ(selection_color_light, GetSelectedColor(browser()));
+  }
 
   // Switch to the default theme without installing a custom theme. E.g. this is
   // what gets used on KDE or when switching to the "classic" theme in settings.
   UseDefaultTheme();
 
-  EXPECT_EQ(selection_color_light, GetSelectedColor(browser()));
+  if (features::IsChromeRefresh2023()) {
+    // TODO(khalidpeer): Delete this clause once CR23 colors are supported on
+    //   themed clients. Currently themed clients fall back to pre-CR23 colors.
+    EXPECT_NE(selection_color_light, GetSelectedColor(browser()));
+  } else {
+    EXPECT_EQ(selection_color_light, GetSelectedColor(browser()));
+  }
 }
 
 // Integration test for omnibox popup theming in Incognito.
diff --git a/chrome/browser/ui/views/page_info/about_this_site_side_panel_coordinator_browsertest.cc b/chrome/browser/ui/views/page_info/about_this_site_side_panel_coordinator_browsertest.cc
index 8855690..833537f0 100644
--- a/chrome/browser/ui/views/page_info/about_this_site_side_panel_coordinator_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/about_this_site_side_panel_coordinator_browsertest.cc
@@ -206,8 +206,8 @@
 
   // Push state with new path.
   GURL kRegularGURL1WithPath2 = kRegularGURL1.Resolve("/title2.html");
-  ASSERT_TRUE(content::ExecuteScript(web_contents(),
-                                     "history.pushState({},'','title2.html')"));
+  ASSERT_TRUE(content::ExecJs(web_contents(),
+                              "history.pushState({},'','title2.html')"));
   EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
 
   // Check that side panel remains open on push state.
@@ -240,8 +240,8 @@
 
   // Replace state with new path.
   GURL kRegularGURL1WithPath2 = kRegularGURL1.Resolve("/title2.html");
-  ASSERT_TRUE(content::ExecuteScript(
-      web_contents(), "history.replaceState({},'','title2.html')"));
+  ASSERT_TRUE(content::ExecJs(web_contents(),
+                              "history.replaceState({},'','title2.html')"));
   EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
 
   // Check that side panel remains open on replace state.
@@ -273,8 +273,8 @@
             SidePanelEntry::Id::kAboutThisSite);
 
   // Replace state with anchor.
-  ASSERT_TRUE(content::ExecuteScript(web_contents(),
-                                     "history.replaceState({},'','#ref')"));
+  ASSERT_TRUE(
+      content::ExecJs(web_contents(), "history.replaceState({},'','#ref')"));
   EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
 
   // Check that side panel remains open on replace state.
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
index 455790d..5fd7615 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
@@ -201,12 +201,14 @@
                       const std::u16string& text,
                       int button_size = kCircularImageButtonSize,
                       bool has_background_color = false,
-                      bool show_border = false)
+                      bool show_border = false,
+                      SkColor themed_icon_color = SK_ColorTRANSPARENT)
       : ImageButton(std::move(callback)),
         icon_(icon),
         button_size_(button_size),
         has_background_color_(has_background_color),
-        show_border_(show_border) {
+        show_border_(show_border),
+        themed_icon_color_(themed_icon_color) {
     SetTooltipText(text);
     views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
 
@@ -228,6 +230,8 @@
       icon_color = color_provider->GetColor(ui::kColorSysOnTonalContainer);
       SetBackground(
           views::CreateRoundedRectBackground(background_color, kButtonRadius));
+    } else if (themed_icon_color_ != SK_ColorTRANSPARENT) {
+      icon_color = themed_icon_color_;
     }
     gfx::ImageSkia image = ImageForMenu(*icon_,
                                         features::IsChromeRefresh2023()
@@ -258,6 +262,13 @@
   // of the menu, all backgrounds are transparent.
   bool has_background_color_;
   bool show_border_;
+  // In the Profile Menu previous to Chrome Refresh 2023, icons that appears on
+  // top of a background with the profile theme color (e.g. edit button) have a
+  // different color than the default icon color. For the default icons, this is
+  // set to transparent and not used.
+  // TODO(crbug.com/1422119): Remove this parameter after Chrome Refresh 2023 is
+  // launched.
+  SkColor themed_icon_color_;
 };
 
 BEGIN_METADATA(CircularImageButton, views::ImageButton)
@@ -627,7 +638,12 @@
         base::BindRepeating(&ProfileMenuViewBase::ButtonPressed,
                             base::Unretained(this),
                             std::move(edit_button_params->edit_action)),
-        *edit_button_params->edit_icon, edit_button_params->edit_tooltip_text);
+        *edit_button_params->edit_icon, edit_button_params->edit_tooltip_text,
+        kCircularImageButtonSize, /*has_background_color=*/false,
+        /*show_border=*/false,
+        avatar_header_art.empty()
+            ? GetProfileForegroundIconColor(profile_background_color)
+            : SK_ColorTRANSPARENT);
   }
 
   BuildProfileBackgroundContainer(
diff --git a/chrome/browser/ui/views/profiles/profile_picker_ui_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_ui_browsertest.cc
index fab1ec5..31af76a8 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_ui_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_ui_browsertest.cc
@@ -28,7 +28,6 @@
 namespace {
 struct ProfilePickerTestParam {
   PixelTestParam pixel_test_param;
-  bool use_tangible_sync_flow = false;
   bool use_multiple_profiles = false;
 };
 
@@ -42,29 +41,16 @@
 
 // Permutations of supported parameters.
 const ProfilePickerTestParam kTestParams[] = {
-    {.pixel_test_param = {.test_suffix = "Default"}},
-    {.pixel_test_param = {.test_suffix = "DefaultMultipleProfiles"},
-     .use_multiple_profiles = true},
-    {.pixel_test_param = {.test_suffix = "DarkRtlSmallMultipleProfiles",
-                          .use_dark_theme = true,
-                          .use_right_to_left_language = true,
-                          .use_small_window = true},
-     .use_multiple_profiles = true},
-    {.pixel_test_param = {.test_suffix = "CR2023",
-                          .use_chrome_refresh_2023_style = true}},
-    {.pixel_test_param = {.test_suffix = "TS"}, .use_tangible_sync_flow = true},
+    {.pixel_test_param = {.test_suffix = "TS"}},
     {.pixel_test_param = {.test_suffix = "TSMultipleProfiles"},
-     .use_tangible_sync_flow = true,
      .use_multiple_profiles = true},
     {.pixel_test_param = {.test_suffix = "DarkRtlSmallTSMultipleProfiles",
                           .use_dark_theme = true,
                           .use_right_to_left_language = true,
                           .use_small_window = true},
-     .use_tangible_sync_flow = true,
      .use_multiple_profiles = true},
     {.pixel_test_param = {.test_suffix = "TSCR2023",
-                          .use_chrome_refresh_2023_style = true},
-     .use_tangible_sync_flow = true},
+                          .use_chrome_refresh_2023_style = true}},
 };
 
 void AddMultipleProfiles(Profile* profile, size_t number_of_profiles) {
@@ -87,12 +73,6 @@
   ProfilePickerUIPixelTest() {
     std::vector<base::test::FeatureRef> enabled_features = {};
     std::vector<base::test::FeatureRef> disabled_features = {};
-    if (GetParam().use_tangible_sync_flow) {
-      enabled_features.push_back(switches::kTangibleSync);
-    } else {
-      disabled_features.push_back(switches::kTangibleSync);
-    }
-
     InitPixelTestFeatures(GetParam().pixel_test_param, scoped_feature_list_,
                           enabled_features, disabled_features);
   }
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.h b/chrome/browser/ui/views/profiles/profile_picker_view.h
index 04964aa4..21dba49 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_VIEW_H_
 
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
diff --git a/chrome/browser/ui/views/profiles/profile_type_choice_ui_browsertest.cc b/chrome/browser/ui/views/profiles/profile_type_choice_ui_browsertest.cc
index a29b7c2..7d97061 100644
--- a/chrome/browser/ui/views/profiles/profile_type_choice_ui_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_type_choice_ui_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <memory>
+
 #include "base/functional/callback_helpers.h"
 #include "base/scoped_environment_variable_override.h"
 #include "base/strings/strcat.h"
@@ -15,8 +16,6 @@
 #include "chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h"
 #include "chrome/browser/ui/views/profiles/profiles_pixel_test_utils.h"
 #include "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
-#include "components/signin/public/base/signin_buildflags.h"
-#include "components/signin/public/base/signin_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -25,34 +24,23 @@
 // Tests for the chrome://profile-picker/new-profile WebUI page. They live here
 // and not in the webui directory because they manipulate views.
 namespace {
-struct ProfileTypeChoiceTestParam {
-  PixelTestParam pixel_test_param;
-  bool use_tangible_sync_flow = false;
-};
 
 // To be passed as 4th argument to `INSTANTIATE_TEST_SUITE_P()`, allows the test
 // to be named like `<TestClassName>.InvokeUi_default/<TestSuffix>` instead
 // of using the index of the param in `TestParam` as suffix.
 std::string ParamToTestSuffix(
-    const ::testing::TestParamInfo<ProfileTypeChoiceTestParam>& info) {
-  return info.param.pixel_test_param.test_suffix;
+    const ::testing::TestParamInfo<PixelTestParam>& info) {
+  return info.param.test_suffix;
 }
 
 // Permutations of supported parameters.
-const ProfileTypeChoiceTestParam kTestParams[] = {
-    {.pixel_test_param = {.test_suffix = "Default"}},
-    {.pixel_test_param = {.test_suffix = "DarkRtlSmall",
-                          .use_dark_theme = true,
-                          .use_right_to_left_language = true,
-                          .use_small_window = true}},
-    {.pixel_test_param = {.test_suffix = "TS"}, .use_tangible_sync_flow = true},
-    {.pixel_test_param = {.test_suffix = "DarkRtlSmallTS",
-                          .use_dark_theme = true,
-                          .use_right_to_left_language = true,
-                          .use_small_window = true},
-     .use_tangible_sync_flow = true},
-    {.pixel_test_param = {.test_suffix = "CR2023",
-                          .use_chrome_refresh_2023_style = true}},
+const PixelTestParam kTestParams[] = {
+    {.test_suffix = "TS"},
+    {.test_suffix = "DarkRtlSmallTS",
+     .use_dark_theme = true,
+     .use_right_to_left_language = true,
+     .use_small_window = true},
+    {.test_suffix = "CR2023", .use_chrome_refresh_2023_style = true},
 };
 
 const char kRemoveAvatarIconJS[] =
@@ -68,24 +56,17 @@
 
 class ProfileTypeChoiceUIPixelTest
     : public UiBrowserTest,
-      public testing::WithParamInterface<ProfileTypeChoiceTestParam> {
+      public testing::WithParamInterface<PixelTestParam> {
  public:
   ProfileTypeChoiceUIPixelTest() {
     std::vector<base::test::FeatureRef> enabled_features = {};
     std::vector<base::test::FeatureRef> disabled_features = {};
-    if (GetParam().use_tangible_sync_flow) {
-      enabled_features.push_back(switches::kTangibleSync);
-    } else {
-      disabled_features.push_back(switches::kTangibleSync);
-    }
-
-    InitPixelTestFeatures(GetParam().pixel_test_param, scoped_feature_list_,
-                          enabled_features, disabled_features);
+    InitPixelTestFeatures(GetParam(), scoped_feature_list_, enabled_features,
+                          disabled_features);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    SetUpPixelTestCommandLine(GetParam().pixel_test_param, scoped_env_override_,
-                              command_line);
+    SetUpPixelTestCommandLine(GetParam(), scoped_env_override_, command_line);
   }
 
   void ShowUi(const std::string& name) override {
@@ -114,7 +95,7 @@
                   host, profile_type_choice_url);
             }));
     profile_picker_view_->ShowAndWait(
-        GetParam().pixel_test_param.use_small_window
+        GetParam().use_small_window
             ? absl::optional<gfx::Size>(gfx::Size(750, 590))
             : absl::nullopt);
     observer.Wait();
diff --git a/chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.cc b/chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.cc
index 85d9fa3..f6f82f3 100644
--- a/chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.cc
+++ b/chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.cc
@@ -35,7 +35,8 @@
   DCHECK(!widget_);
   if (on_visible_)
     BrowserList::RemoveObserver(this);
-  CHECK(!IsInObserverList());
+  CHECK(!WidgetObserver::IsInObserverList());
+  CHECK(!BrowserListObserver::IsInObserverList());
 }
 
 void RelaunchNotificationControllerPlatformImpl::NotifyRelaunchRecommended(
diff --git a/chrome/browser/ui/views/select_file_dialog_extension.h b/chrome/browser/ui/views/select_file_dialog_extension.h
index 2031411..6b2978c 100644
--- a/chrome/browser/ui/views/select_file_dialog_extension.h
+++ b/chrome/browser/ui/views/select_file_dialog_extension.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_file_destination.h"
diff --git a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_browsertest.cc b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_browsertest.cc
index b584652..ca8df6ae 100644
--- a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_browsertest.cc
@@ -240,7 +240,7 @@
 
     std::string script =
         base::StringPrintf(R"(document.sidePanelTemp = "%s";)", value.c_str());
-    ASSERT_TRUE(content::ExecuteScript(
+    ASSERT_TRUE(content::ExecJs(
         extension_coordinator->GetHostWebContentsForTesting(), script.c_str()));
   }
 
@@ -535,9 +535,9 @@
   {
     content::WebContentsDestroyedWatcher destroyed_watcher(
         extension_coordinator->GetHostWebContentsForTesting());
-    ASSERT_TRUE(content::ExecuteScript(
-        extension_coordinator->GetHostWebContentsForTesting(),
-        "window.close();"));
+    ASSERT_TRUE(
+        content::ExecJs(extension_coordinator->GetHostWebContentsForTesting(),
+                        "window.close();"));
     destroyed_watcher.Wait();
   }
 
@@ -558,9 +558,9 @@
   // view when the extension panel is not shown.
   content::WebContentsDestroyedWatcher destroyed_watcher(
       extension_coordinator->GetHostWebContentsForTesting());
-  ASSERT_TRUE(content::ExecuteScript(
-      extension_coordinator->GetHostWebContentsForTesting(),
-      "window.close();"));
+  ASSERT_TRUE(
+      content::ExecJs(extension_coordinator->GetHostWebContentsForTesting(),
+                      "window.close();"));
   destroyed_watcher.Wait();
 
   // The side panel should be open because the reading list entry is still
@@ -603,9 +603,9 @@
           ->GetExtensionCoordinatorForTesting(extension->id());
   content::WebContentsDestroyedWatcher destroyed_watcher(
       extension_coordinator->GetHostWebContentsForTesting());
-  ASSERT_TRUE(content::ExecuteScript(
-      extension_coordinator->GetHostWebContentsForTesting(),
-      "window.close();"));
+  ASSERT_TRUE(
+      content::ExecJs(extension_coordinator->GetHostWebContentsForTesting(),
+                      "window.close();"));
   destroyed_watcher.Wait();
 }
 
@@ -641,9 +641,9 @@
 
     content::WebContentsDestroyedWatcher destroyed_watcher(
         extension_coordinator->GetHostWebContentsForTesting());
-    ASSERT_TRUE(content::ExecuteScript(
-        extension_coordinator->GetHostWebContentsForTesting(),
-        "window.close();"));
+    ASSERT_TRUE(
+        content::ExecJs(extension_coordinator->GetHostWebContentsForTesting(),
+                        "window.close();"));
     destroyed_watcher.Wait();
   }
 
diff --git a/chrome/browser/ui/views/side_panel/lens/lens_core_tab_side_panel_helper.cc b/chrome/browser/ui/views/side_panel/lens/lens_core_tab_side_panel_helper.cc
index 1560ffef..7c51645 100644
--- a/chrome/browser/ui/views/side_panel/lens/lens_core_tab_side_panel_helper.cc
+++ b/chrome/browser/ui/views/side_panel/lens/lens_core_tab_side_panel_helper.cc
@@ -6,6 +6,8 @@
 
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/views/side_panel/side_panel.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
 #include "components/lens/buildflags.h"
 #include "components/lens/lens_features.h"
 #include "components/search/search.h"
@@ -17,6 +19,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/side_panel/companion/companion_utils.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
 namespace lens {
@@ -53,6 +56,18 @@
 
 }  // namespace internal
 
+gfx::Size GetSidePanelInitialContentSizeUpperBound(
+    content::WebContents* web_contents) {
+#if !BUILDFLAG(IS_ANDROID)
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+  const SidePanel* side_panel =
+      BrowserView::GetBrowserViewForBrowser(browser)->unified_side_panel();
+  return side_panel->GetContentSizeUpperBound();
+#else
+  return gfx::Size();
+#endif  // !BUILDFLAG(IS_ANDROID)
+}
+
 bool IsSidePanelEnabledForLens(content::WebContents* web_contents) {
   // Companion feature being enabled should disable Lens in the side panel.
   bool is_companion_enabled = false;
diff --git a/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc b/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc
index ac0bdff..b2093c4e 100644
--- a/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc
@@ -212,8 +212,17 @@
   base::UserActionTester user_action_tester;
 };
 
-IN_PROC_BROWSER_TEST_F(SearchImageWithUnifiedSidePanel,
-                       ImageSearchWithValidImageOpensUnifiedSidePanelForLens) {
+// https://crbug.com/1444953
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanelForLens \
+  DISABLED_ImageSearchWithValidImageOpensUnifiedSidePanelForLens
+#else
+#define MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanelForLens \
+  ImageSearchWithValidImageOpensUnifiedSidePanelForLens
+#endif
+IN_PROC_BROWSER_TEST_F(
+    SearchImageWithUnifiedSidePanel,
+    MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanelForLens) {
   SetupUnifiedSidePanel();
   EXPECT_TRUE(GetUnifiedSidePanel()->GetVisible());
 
diff --git a/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc b/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
index 5076be5dd..0b9c67b 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/companion/core/proto/companion_url_params.pb.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/side_panel/companion/companion_tab_helper.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h"
@@ -615,7 +616,7 @@
   side_panel_coordinator()->Close();
   ExpectUkmEntry(
       &ukm_recorder, ukm::builders::Companion_PageView::kOpenTriggerName,
-      static_cast<int>(companion::OpenTrigger::kContextMenuTextSearch));
+      static_cast<int>(SidePanelOpenTrigger::kContextMenuSearchOption));
 }
 
 IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
@@ -648,9 +649,9 @@
 
   // Close side panel and verify UKM.
   side_panel_coordinator()->Close();
-  ExpectUkmEntry(
-      &ukm_recorder, ukm::builders::Companion_PageView::kOpenTriggerName,
-      static_cast<int>(companion::OpenTrigger::kContextMenuImageSearch));
+  ExpectUkmEntry(&ukm_recorder,
+                 ukm::builders::Companion_PageView::kOpenTriggerName,
+                 static_cast<int>(SidePanelOpenTrigger::kLensContextMenu));
 }
 
 IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, OpenedFromEntryPoint) {
@@ -661,8 +662,9 @@
       ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
   ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), absl::nullopt);
 
-  // Open companion from entry point.
-  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
+  // Open companion from entry point via dropdown.
+  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
+                                 SidePanelOpenTrigger::kComboboxSelected);
   EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());
 
   WaitForCompanionToBeLoaded();
@@ -673,7 +675,7 @@
   side_panel_coordinator()->Close();
   ExpectUkmEntry(&ukm_recorder,
                  ukm::builders::Companion_PageView::kOpenTriggerName,
-                 static_cast<int>(companion::OpenTrigger::kOther));
+                 static_cast<int>(SidePanelOpenTrigger::kComboboxSelected));
 }
 
 IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
@@ -684,8 +686,10 @@
       ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
   ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), absl::nullopt);
 
-  // Open companion from entry point.
-  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
+  // Open companion from pinned entry point.
+  side_panel_coordinator()->Show(
+      SidePanelEntry::Id::kSearchCompanion,
+      SidePanelOpenTrigger::kPinnedEntryToolbarButton);
   EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());
 
   WaitForCompanionToBeLoaded();
@@ -703,7 +707,7 @@
 
   // Close side panel and verify UKM.
   side_panel_coordinator()->Close();
-  ExpectUkmEntry(&ukm_recorder,
-                 ukm::builders::Companion_PageView::kOpenTriggerName,
-                 static_cast<int>(companion::OpenTrigger::kOther));
+  ExpectUkmEntry(
+      &ukm_recorder, ukm::builders::Companion_PageView::kOpenTriggerName,
+      static_cast<int>(SidePanelOpenTrigger::kPinnedEntryToolbarButton));
 }
diff --git a/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.cc b/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.cc
index 5ad369a..8b67cb5 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.cc
@@ -58,11 +58,12 @@
       SidePanelEntry::Key(SidePanelEntry::Id::kSearchCompanion));
 }
 
-void CompanionSidePanelController::ShowCompanionSidePanel() {
+void CompanionSidePanelController::ShowCompanionSidePanel(
+    SidePanelOpenTrigger side_panel_open_trigger) {
   if (Browser* browser = chrome::FindBrowserWithWebContents(web_contents_)) {
     auto* coordinator =
         SearchCompanionSidePanelCoordinator::GetOrCreateForBrowser(browser);
-    coordinator->Show();
+    coordinator->Show(side_panel_open_trigger);
   }
 }
 
diff --git a/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.h b/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.h
index de1c4167..eb56bafb 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.h
+++ b/chrome/browser/ui/views/side_panel/search_companion/companion_side_panel_controller.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/side_panel/companion/companion_tab_helper.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "content/public/browser/web_contents_observer.h"
 
 namespace content {
@@ -32,7 +33,8 @@
   // CompanionTabHelper::Delegate:
   void CreateAndRegisterEntry() override;
   void DeregisterEntry() override;
-  void ShowCompanionSidePanel() override;
+  void ShowCompanionSidePanel(
+      SidePanelOpenTrigger side_panel_open_trigger) override;
   void UpdateNewTabButton(GURL url_to_open) override;
   content::WebContents* GetCompanionWebContentsForTesting() override;
 
diff --git a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
index 7fdb1f7..5c58962 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/side_panel/companion/companion_tab_helper.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
@@ -52,14 +53,16 @@
          (!include_dsp_check || search::DefaultSearchProviderIsGoogle(profile));
 }
 
-bool SearchCompanionSidePanelCoordinator::Show() {
+bool SearchCompanionSidePanelCoordinator::Show(
+    SidePanelOpenTrigger side_panel_open_trigger) {
   auto* browser_view = GetBrowserView();
   if (!browser_view) {
     return false;
   }
 
   if (auto* side_panel_coordinator = browser_view->side_panel_coordinator()) {
-    side_panel_coordinator->Show(SidePanelEntry::Id::kSearchCompanion);
+    side_panel_coordinator->Show(SidePanelEntry::Id::kSearchCompanion,
+                                 side_panel_open_trigger);
   }
 
   return true;
@@ -74,6 +77,14 @@
   return l10n_util::GetStringUTF16(IDS_SIDE_PANEL_COMPANION_TOOLBAR_TOOLTIP);
 }
 
+void SearchCompanionSidePanelCoordinator::NotifyCompanionOfSidePanelOpenTrigger(
+    absl::optional<SidePanelOpenTrigger> side_panel_open_trigger) {
+  auto* companion_tab_helper = companion::CompanionTabHelper::FromWebContents(
+      browser_->tab_strip_model()->GetActiveWebContents());
+  companion_tab_helper->SetMostRecentSidePanelOpenTrigger(
+      side_panel_open_trigger);
+}
+
 void SearchCompanionSidePanelCoordinator::OnTabStripModelChanged(
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
diff --git a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
index 45272535..011a6dd 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
@@ -11,6 +11,7 @@
 #include "base/memory/raw_ref.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/ui/browser_user_data.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
@@ -36,7 +37,7 @@
 
   static bool IsSupported(Profile* profile, bool include_dsp_check = true);
 
-  bool Show();
+  bool Show(SidePanelOpenTrigger side_panel_open_trigger);
   BrowserView* GetBrowserView();
   std::u16string GetTooltipForToolbarButton();
 
@@ -49,6 +50,10 @@
       const TabStripModelChange& change,
       const TabStripSelectionChange& selection) override;
 
+  // For metrics only. Notifies the companion of the side panel open trigger.
+  void NotifyCompanionOfSidePanelOpenTrigger(
+      absl::optional<SidePanelOpenTrigger> side_panel_open_trigger);
+
  private:
   friend class BrowserUserData<SearchCompanionSidePanelCoordinator>;
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index 7586085..b997dce7d 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -220,6 +220,15 @@
                    min_height);
 }
 
+gfx::Size SidePanel::GetContentSizeUpperBound() const {
+  const int side_panel_width = width() > 0 ? width() : GetMinimumSize().width();
+  const int side_panel_height =
+      height() > 0 ? height() : browser_view_->height();
+
+  return gfx::Size(std::max(0, side_panel_width - kBorderInsets.width()),
+                   std::max(0, side_panel_height - kBorderInsets.height()));
+}
+
 void SidePanel::ChildVisibilityChanged(View* child) {
   UpdateVisibility();
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel.h b/chrome/browser/ui/views/side_panel/side_panel.h
index fc411e9b..f784fed 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.h
+++ b/chrome/browser/ui/views/side_panel/side_panel.h
@@ -38,6 +38,13 @@
   bool IsRightAligned();
   gfx::Size GetMinimumSize() const override;
 
+  // Gets the upper bound of the content area size if the side panel is shown
+  // right now. If the side panel is not showing, returns the minimum width
+  // and browser view height minus the padding insets. The actual content
+  // size will be smaller than the returned result when the side panel header
+  // is shown, for example.
+  gfx::Size GetContentSizeUpperBound() const;
+
   // views::ResizeAreaDelegate:
   void OnResize(int resize_amount, bool done_resizing) override;
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
index ed07d3f..c697afb 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
@@ -440,8 +440,8 @@
     return;
   }
 
-  SidePanelUtil::RecordEntryShowTriggeredMetrics(entry->key().id(),
-                                                 open_trigger);
+  SidePanelUtil::RecordEntryShowTriggeredMetrics(
+      browser_view_->browser(), entry->key().id(), open_trigger);
 
   content_wrapper->RequestEntry(
       entry, base::BindOnce(&SidePanelCoordinator::PopulateSidePanel,
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
index 7d3d964..69a48c4f 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_COORDINATOR_H_
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_COORDINATOR_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/observer_list.h"
 #include "base/scoped_multi_source_observation.h"
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index f059386..8addf08 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.h"
 #include "components/feed/feed_feature_list.h"
 #include "components/history_clusters/core/features.h"
-#include "components/history_clusters/core/history_clusters_prefs.h"
 #include "components/history_clusters/core/history_clusters_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_notes/user_notes_features.h"
@@ -78,13 +77,8 @@
 
   // Add history clusters.
   if (HistoryClustersSidePanelCoordinator::IsSupported(browser->profile())) {
-    auto* history_clusters_side_panel_coordinator =
-        HistoryClustersSidePanelCoordinator::GetOrCreateForBrowser(browser);
-    if (browser->profile()->GetPrefs()->GetBoolean(
-            history_clusters::prefs::kVisible)) {
-      history_clusters_side_panel_coordinator->CreateAndRegisterEntry(
-          global_registry);
-    }
+    HistoryClustersSidePanelCoordinator::GetOrCreateForBrowser(browser)
+        ->CreateAndRegisterEntry(global_registry);
   }
 
   // Add read anything.
@@ -194,6 +188,7 @@
 }
 
 void SidePanelUtil::RecordEntryShowTriggeredMetrics(
+    Browser* browser,
     SidePanelEntry::Id id,
     absl::optional<SidePanelUtil::SidePanelOpenTrigger> trigger) {
   if (trigger.has_value()) {
@@ -202,4 +197,11 @@
             {"SidePanel.", GetHistogramNameForId(id), ".ShowTriggered"}),
         trigger.value());
   }
+
+  if (id == SidePanelEntry::Id::kSearchCompanion) {
+    auto* search_companion_coordinator =
+        SearchCompanionSidePanelCoordinator::GetOrCreateForBrowser(browser);
+    search_companion_coordinator->NotifyCompanionOfSidePanelOpenTrigger(
+        trigger);
+  }
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.h b/chrome/browser/ui/views/side_panel/side_panel_util.h
index 93bb542..3e8237d 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTIL_H_
 
 #include "base/time/time.h"
-#include "chrome/browser/ui/side_panel/side_panel_open_trigger.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -46,6 +46,7 @@
   static void RecordEntryHiddenMetrics(SidePanelEntry::Id id,
                                        base::TimeTicks shown_timestamp);
   static void RecordEntryShowTriggeredMetrics(
+      Browser* browser,
       SidePanelEntry::Id id,
       absl::optional<SidePanelOpenTrigger> trigger);
 };
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
index bbc546b..d2ec0cff 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/media/webrtc/capture_policy_utils.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/same_origin_observer.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -62,6 +63,13 @@
              "ShareThisTabInsteadSelfCapture",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Killswitch for bypassing the new logic for fixing tab-sharing indicators
+// for guest mode, in case an unexpected bug is discovered.
+// TODO(crbug.com/1443411): Remove this.
+BASE_FEATURE(kTabShareInGuestModeBugfix,
+             "TabShareInGuestModeBugfix",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 #if BUILDFLAG(IS_CHROMEOS)
 bool g_apply_dlp_for_all_users_for_testing_ = false;
 #endif
@@ -144,6 +152,7 @@
     bool app_preferred_current_tab,
     TabSharingInfoBarDelegate::TabShareType capture_type)
     : capture_session_id_(next_capture_session_id_++),
+      profile_(ProfileManager::GetLastUsedProfileAllowedByPolicy()),
       capturer_(capturer),
       capturer_origin_(GetOriginFromId(capturer)),
       can_focus_capturer_(GetOriginFromId(capturer).scheme() !=
@@ -163,7 +172,6 @@
 
   Observe(shared_tab_);
   shared_tab_name_ = GetTabName(shared_tab_);
-  profile_ = ProfileManager::GetLastUsedProfileAllowedByPolicy();
 
   if (capturer_restricted_to_same_origin_) {
     // base::Unretained is safe here because we own the origin observer, so it
@@ -233,8 +241,12 @@
 }
 
 void TabSharingUIViews::OnBrowserAdded(Browser* browser) {
-  if (browser->profile()->GetOriginalProfile() == profile_)
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  CHECK(browser);
+
+  if (IsCapturableByCapturer(browser->profile())) {
     browser->tab_strip_model()->AddObserver(this);
+  }
 }
 
 void TabSharingUIViews::OnBrowserRemoved(Browser* browser) {
@@ -355,6 +367,13 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   BrowserList* browser_list = BrowserList::GetInstance();
   for (auto* browser : *browser_list) {
+    CHECK(browser);
+
+    if (base::FeatureList::IsEnabled(kTabShareInGuestModeBugfix) &&
+        !IsCapturableByCapturer(browser->profile())) {
+      continue;
+    }
+
     OnBrowserAdded(browser);
 
     TabStripModel* tab_strip_model = browser->tab_strip_model();
@@ -631,3 +650,21 @@
   return base::FeatureList::IsEnabled(kShareThisTabInsteadSelfCapture) &&
          app_preferred_current_tab_;
 }
+
+bool TabSharingUIViews::IsCapturableByCapturer(const Profile* profile) const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  CHECK(profile);
+
+  if (base::FeatureList::IsEnabled(kTabShareInGuestModeBugfix)) {
+    // Guest profiles may have an arbitrary non-guest profile as their original,
+    // so direct comparison would not work. Instead, we rely on the assumption
+    // that there is at most one guest profile.
+    const bool capturer_is_guest = profile_ && profile_->IsGuestSession();
+    const bool new_is_guest = profile->IsGuestSession();
+    if (capturer_is_guest || new_is_guest) {
+      return capturer_is_guest && new_is_guest;
+    }
+  }
+
+  return profile->GetOriginalProfile() == profile_;
+}
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
index 80cafd34..af0b59f 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
@@ -160,6 +160,14 @@
   // Whether the share-this-tab-instead button may be shown for |web_contents|.
   bool IsShareInsteadButtonPossible(content::WebContents* web_contents) const;
 
+  // Tabs eligible for capture include:
+  // * Tabs from the same profile.
+  // * Tabs from an incognito profile may capture the original profile's tabs,
+  //   and vice versa.
+  // * Guest tabs may only capture other guest tabs. (Note that a guest tab's
+  //   "original" session might be an arbitrary non-guest session.)
+  bool IsCapturableByCapturer(const Profile* profile) const;
+
   // As for the purpose of this identification:
   // Assume a tab is captured twice, and both sessions use Region Capture.
   // The blue border falls back on its viewport-encompassing form. But when
@@ -168,6 +176,9 @@
   static CaptureSessionId next_capture_session_id_;
   const CaptureSessionId capture_session_id_;
 
+  // The capturer's profile.
+  const raw_ptr<Profile, DanglingUntriaged> profile_;
+
   InfoBars infobars_;
   std::map<content::WebContents*, std::unique_ptr<SameOriginObserver>>
       same_origin_observers_;
@@ -183,7 +194,6 @@
   raw_ptr<content::WebContents, DanglingUntriaged> shared_tab_;
   std::unique_ptr<SameOriginObserver> shared_tab_origin_observer_;
   std::u16string shared_tab_name_;
-  raw_ptr<Profile, DanglingUntriaged> profile_;
   std::unique_ptr<content::MediaStreamUI> tab_capture_indicator_ui_;
 
   // FaviconPeriodicUpdate() runs on a delayed task which re-posts itself.
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc
index e75ce433..059f3e5 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc
@@ -9,6 +9,7 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_features.h"
@@ -142,6 +143,7 @@
   browser->tab_strip_model()->ActivateTabAt(
       tab, TabStripUserGestureDetails(
                TabStripUserGestureDetails::GestureType::kMouse));
+  base::RunLoop().RunUntilIdle();
 }
 
 constexpr int kNullTabIndex = -1;
@@ -208,6 +210,8 @@
         base::BindRepeating(&TabSharingUIViewsBrowserTest::OnStartSharing,
                             base::Unretained(this)),
         std::vector<content::DesktopMediaID>{});
+
+    base::RunLoop().RunUntilIdle();
   }
 
   struct UiExpectations {
@@ -296,6 +300,8 @@
       CreateUniqueFaviconFor(
           browser->tab_strip_model()->GetWebContentsAt(next_index));
     }
+
+    base::RunLoop().RunUntilIdle();
   }
 
   void CreateUniqueFaviconFor(content::WebContents* web_contents) {
@@ -585,6 +591,149 @@
                           .has_border = false});
 }
 
+// TODO(crbug/1444833): Re-enable once passing
+// TODO(crbug.com/1444732): Enable on CrOS.
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
+    !BUILDFLAG(IS_CHROMEOS_LACROS)
+IN_PROC_BROWSER_TEST_P(
+    TabSharingUIViewsBrowserTest,
+    DISABLED_NormalModeCapturerDoesNotProduceInfobarInGuestModeTabOpenedBeforeCapture) {
+  // Create a guest-mode browser.
+  Browser* const guest_browser = CreateGuestBrowser();
+  AddTabs(guest_browser, 1);
+  ASSERT_EQ(guest_browser->tab_strip_model()->count(), 2);
+
+  // Create a normal-mode browser.
+  Browser* const main_browser = CreateBrowser(browser()->profile());
+  AddTabs(main_browser, 1);
+  ASSERT_EQ(main_browser->tab_strip_model()->count(), 2);
+
+  // Start a capture in the normal-mode capture.
+  CreateUiAndStartSharing(main_browser, /*capturing_tab=*/0,
+                          /*captured_tab=*/1);
+
+  // Expectation #1: The capture infobar is created in the profile
+  // where capture is happening.
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/0)->infobar_count(), 1u);
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/1)->infobar_count(), 1u);
+
+  // Expectation #2: The capture infobar is NOT created in the profile
+  // where capture is NOT happening.
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/0)->infobar_count(), 0u);
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/1)->infobar_count(), 0u);
+}
+
+// TODO(crbug/1444833): Re-enable once passing
+IN_PROC_BROWSER_TEST_P(
+    TabSharingUIViewsBrowserTest,
+    DISABLED_NormalModeCapturerDoesNotProduceInfobarInGuestModeTabOpenedAfterCapture) {
+  // Create a normal-mode browser.
+  Browser* const main_browser = CreateBrowser(browser()->profile());
+  AddTabs(main_browser, 1);
+  ASSERT_EQ(main_browser->tab_strip_model()->count(), 2);
+
+  // Start a capture in the normal-mode capture.
+  CreateUiAndStartSharing(main_browser, /*capturing_tab=*/0,
+                          /*captured_tab=*/1);
+
+  // Create a guest-mode browser.
+  Browser* const guest_browser = CreateGuestBrowser();
+  AddTabs(guest_browser, 1);
+  ASSERT_EQ(guest_browser->tab_strip_model()->count(), 2);
+
+  // Expectation #1: The capture infobar is created in the profile
+  // where capture is happening.
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/0)->infobar_count(), 1u);
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/1)->infobar_count(), 1u);
+
+  // Expectation #2: The capture infobar is NOT created in the profile
+  // where capture is NOT happening.
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/0)->infobar_count(), 0u);
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/1)->infobar_count(), 0u);
+}
+
+// TODO(crbug/1444833): Re-enable once passing
+IN_PROC_BROWSER_TEST_P(
+    TabSharingUIViewsBrowserTest,
+    DISABLED_GuestModeCapturerDoesNotProduceInfobarInNormalModeTabOpenedBeforeCapture) {
+  // Create a normal-mode browser.
+  Browser* const main_browser = CreateBrowser(browser()->profile());
+  AddTabs(main_browser, 1);
+  ASSERT_EQ(main_browser->tab_strip_model()->count(), 2);
+
+  // Create a guest-mode browser.
+  Browser* const guest_browser = CreateGuestBrowser();
+  AddTabs(guest_browser, 1);
+  ASSERT_EQ(guest_browser->tab_strip_model()->count(), 2);
+
+  // Start a capture in the guest-mode browser.
+  CreateUiAndStartSharing(guest_browser, /*capturing_tab=*/0,
+                          /*captured_tab=*/1);
+
+  // Expectation #1: The capture infobar is created in the profile
+  // where capture is happening.
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/0)->infobar_count(), 1u);
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/1)->infobar_count(), 1u);
+
+  // Expectation #2: The capture infobar is NOT created in the profile
+  // where capture is NOT happening.
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/0)->infobar_count(), 0u);
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/1)->infobar_count(), 0u);
+}
+
+// TODO(crbug/1444833): Re-enable once passing
+IN_PROC_BROWSER_TEST_P(
+    TabSharingUIViewsBrowserTest,
+    DISABLED_GuestModeCapturerDoesNotProduceInfobarInNormalModeTabOpenedAfterCapture) {
+  // Create a guest-mode browser.
+  Browser* const guest_browser = CreateGuestBrowser();
+  AddTabs(guest_browser, 1);
+  ASSERT_EQ(guest_browser->tab_strip_model()->count(), 2);
+
+  // Start a capture in the guest-mode browser.
+  CreateUiAndStartSharing(guest_browser, /*capturing_tab=*/0,
+                          /*captured_tab=*/1);
+
+  // Create a normal-mode browser.
+  Browser* const main_browser = CreateBrowser(browser()->profile());
+  AddTabs(main_browser, 1);
+  ASSERT_EQ(main_browser->tab_strip_model()->count(), 2);
+
+  // Expectation #1: The capture infobar is created in the profile
+  // where capture is happening.
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/0)->infobar_count(), 1u);
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/1)->infobar_count(), 1u);
+
+  // Expectation #2: The capture infobar is NOT created in the profile
+  // where capture is NOT happening.
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/0)->infobar_count(), 0u);
+  EXPECT_EQ(GetInfoBarManager(main_browser, /*tab=*/1)->infobar_count(), 0u);
+}
+
+// TODO(crbug/1444833): Re-enable once passing
+IN_PROC_BROWSER_TEST_P(
+    TabSharingUIViewsBrowserTest,
+    DISABLED_TabsAddedInGuestModeHaveInfobarIfGuestModeCapture) {
+  // Create a guest-mode browser.
+  Browser* const guest_browser = CreateGuestBrowser();
+  AddTabs(guest_browser, 1);
+  ASSERT_EQ(guest_browser->tab_strip_model()->count(), 2);
+
+  // Start a capture in the guest-mode browser.
+  CreateUiAndStartSharing(guest_browser, /*capturing_tab=*/0,
+                          /*captured_tab=*/1);
+
+  // Sanity - existing tabs have an infobar.
+  ASSERT_EQ(GetInfoBarManager(guest_browser, /*tab=*/0)->infobar_count(), 1u);
+  ASSERT_EQ(GetInfoBarManager(guest_browser, /*tab=*/1)->infobar_count(), 1u);
+
+  // Test focus - when adding a tab in guest mode, that tab has an infobar.
+  AddTabs(guest_browser, 1);
+  ASSERT_EQ(guest_browser->tab_strip_model()->count(), 3);
+  EXPECT_EQ(GetInfoBarManager(guest_browser, /*tab=*/2)->infobar_count(), 1u);
+}
+#endif
+
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, KillTab) {
   AddTabs(browser(), 2);
   ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
index 60ed735d..9a4f5a8 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
@@ -52,6 +52,10 @@
 #include "ui/base/ime/virtual_keyboard_controller.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+namespace {
+constexpr int kChromeRefreshImageLabelPadding = 2;
+}
+
 // static
 bool BrowserAppMenuButton::g_open_app_immediately_for_testing = false;
 
@@ -60,6 +64,9 @@
                                         base::Unretained(this))),
       toolbar_view_(toolbar_view) {
   SetHorizontalAlignment(gfx::ALIGN_RIGHT);
+  if (features::IsChromeRefresh2023()) {
+    SetImageLabelSpacing(kChromeRefreshImageLabelPadding);
+  }
 }
 
 BrowserAppMenuButton::~BrowserAppMenuButton() {}
@@ -128,6 +135,9 @@
   // Call `UpdateIcon()` after `UpdateTextAndHighlightColor()` as the icon color
   // depends on if the container is in an expanded state.
   UpdateIcon();
+  if (features::IsChromeRefresh2023()) {
+    UpdateInkdrop();
+  }
 }
 
 void BrowserAppMenuButton::UpdateIcon() {
@@ -144,6 +154,18 @@
   }
 }
 
+void BrowserAppMenuButton::UpdateInkdrop() {
+  CHECK(features::IsChromeRefresh2023());
+
+  if (IsLabelPresentAndVisible()) {
+    ConfigureToolbarInkdropForRefresh2023(this, kColorAppMenuChipInkDropHover,
+                                          kColorAppMenuChipInkDropRipple);
+  } else {
+    ConfigureToolbarInkdropForRefresh2023(this, kColorToolbarInkDropHover,
+                                          kColorToolbarInkDropRipple);
+  }
+}
+
 bool BrowserAppMenuButton::IsLabelPresentAndVisible() const {
   if (!label()) {
     return false;
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.h b/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
index 9f13b2f8..50286845 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
@@ -30,6 +30,8 @@
   BrowserAppMenuButton& operator=(const BrowserAppMenuButton&) = delete;
   ~BrowserAppMenuButton() override;
 
+  // Returns true if a text is set and is visible.
+  bool IsLabelPresentAndVisible() const;
   void SetTypeAndSeverity(
       AppMenuIconController::TypeAndSeverity type_and_severity);
 
@@ -43,6 +45,10 @@
 
   void UpdateColors();
 
+  // Updates the inkdrop highlight and ripple properties depending on whether
+  // the chip is expanded.
+  void UpdateInkdrop();
+
   // AppMenuButton:
   void OnThemeChanged() override;
   // Updates the presentation according to |severity_| and the theme provider.
@@ -59,7 +65,6 @@
   bool ShouldPaintBorder() const override;
   absl::optional<SkColor> GetHighlightTextColor() const override;
 
-  bool IsLabelPresentAndVisible() const;
   SkColor GetForegroundColor(ButtonState state) const override;
   void SetHasInProductHelpPromo(bool has_in_product_help_promo);
 
diff --git a/chrome/browser/ui/views/toolbar/side_panel_toolbar_button.h b/chrome/browser/ui/views/toolbar/side_panel_toolbar_button.h
index 8120502..ad18eff 100644
--- a/chrome/browser/ui/views/toolbar/side_panel_toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/side_panel_toolbar_button.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TOOLBAR_SIDE_PANEL_TOOLBAR_BUTTON_H_
 #define CHROME_BROWSER_UI_VIEWS_TOOLBAR_SIDE_PANEL_TOOLBAR_BUTTON_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/views/bubble/bubble_contents_wrapper.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
index 629b306..7a557cf2 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
@@ -86,43 +86,48 @@
       base::BindRepeating(&GetToolbarInkDropBaseColor, host));
 
   if (features::IsChromeRefresh2023()) {
-    views::InkDrop::Get(host)->SetLayerRegion(views::LayerRegion::kAbove);
-    views::InkDrop::Get(host)->SetCreateRippleCallback(base::BindRepeating(
-        [](views::Button* host) -> std::unique_ptr<views::InkDropRipple> {
-          // TODO(shibalik): Replace with a local color token once theme colors
-          // is handled with chrome refresh.
-          const auto* color_provider = host->GetColorProvider();
-          const SkColor pressed_color =
-              color_provider
-                  ? color_provider->GetColor(kColorToolbarInkDropRipple)
-                  : gfx::kPlaceholderColor;
-          const float pressed_alpha = SkColorGetA(pressed_color);
-
-          return std::make_unique<views::FloodFillInkDropRipple>(
-              views::InkDrop::Get(host), host->size(),
-              host->GetLocalBounds().CenterPoint(),
-              SkColorSetA(pressed_color, SK_AlphaOPAQUE),
-              pressed_alpha / SK_AlphaOPAQUE);
-        },
-        host));
-    views::InkDrop::Get(host)->SetCreateHighlightCallback(base::BindRepeating(
-        [](views::Button* host) {
-          // TODO(shibalik): Replace with a local color token once theme colors
-          // is handled with chrome refresh.
-          const auto* color_provider = host->GetColorProvider();
-          const SkColor hover_color =
-              color_provider
-                  ? color_provider->GetColor(kColorToolbarInkDropHover)
-                  : gfx::kPlaceholderColor;
-          const float hover_alpha = SkColorGetA(hover_color);
-
-          auto ink_drop_highlight = std::make_unique<views::InkDropHighlight>(
-              host->size(), host->height() / 2,
-              gfx::PointF(host->GetLocalBounds().CenterPoint()),
-              SkColorSetA(hover_color, SK_AlphaOPAQUE));
-          ink_drop_highlight->set_visible_opacity(hover_alpha / SK_AlphaOPAQUE);
-          return ink_drop_highlight;
-        },
-        host));
+    ConfigureToolbarInkdropForRefresh2023(host, kColorToolbarInkDropHover,
+                                          kColorToolbarInkDropRipple);
   }
 }
+
+void ConfigureToolbarInkdropForRefresh2023(
+    views::View* const host,
+    const ChromeColorIds hover_color_id,
+    const ChromeColorIds ripple_color_id) {
+  CHECK(features::IsChromeRefresh2023());
+  views::InkDrop::Get(host)->SetLayerRegion(views::LayerRegion::kAbove);
+  views::InkDrop::Get(host)->SetCreateRippleCallback(base::BindRepeating(
+      [](views::View* host, ChromeColorIds ripple_color_id)
+          -> std::unique_ptr<views::InkDropRipple> {
+        const auto* color_provider = host->GetColorProvider();
+        const SkColor pressed_color =
+            color_provider ? color_provider->GetColor(ripple_color_id)
+                           : gfx::kPlaceholderColor;
+        const float pressed_alpha = SkColorGetA(pressed_color);
+
+        return std::make_unique<views::FloodFillInkDropRipple>(
+            views::InkDrop::Get(host), host->size(),
+            host->GetLocalBounds().CenterPoint(),
+            SkColorSetA(pressed_color, SK_AlphaOPAQUE),
+            pressed_alpha / SK_AlphaOPAQUE);
+      },
+      host, ripple_color_id));
+
+  views::InkDrop::Get(host)->SetCreateHighlightCallback(base::BindRepeating(
+      [](views::View* host, ChromeColorIds hover_color_id) {
+        const auto* color_provider = host->GetColorProvider();
+        const SkColor hover_color =
+            color_provider ? color_provider->GetColor(hover_color_id)
+                           : gfx::kPlaceholderColor;
+        const float hover_alpha = SkColorGetA(hover_color);
+
+        auto ink_drop_highlight = std::make_unique<views::InkDropHighlight>(
+            host->size(), host->height() / 2,
+            gfx::PointF(host->GetLocalBounds().CenterPoint()),
+            SkColorSetA(hover_color, SK_AlphaOPAQUE));
+        ink_drop_highlight->set_visible_opacity(hover_alpha / SK_AlphaOPAQUE);
+        return ink_drop_highlight;
+      },
+      host, hover_color_id));
+}
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
index 6445a7f..fa81b0f6 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
 #define CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
 
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/point.h"
@@ -26,4 +27,10 @@
 
 void ConfigureInkDropForToolbar(views::Button* host);
 
+// Sets the highlight color callback and ripple color callback for inkdrop when
+// the chrome refresh flag is on.
+void ConfigureToolbarInkdropForRefresh2023(views::View* host,
+                                           ChromeColorIds hover_color_id,
+                                           ChromeColorIds ripple_color_id);
+
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util_unittest.cc b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util_unittest.cc
new file mode 100644
index 0000000..8f70959
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/color/chrome_color_provider_utils.h"
+#include "chrome/test/views/chrome_views_test_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_features.h"
+#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
+#include "ui/views/animation/ink_drop.h"
+#include "ui/views/animation/ink_drop_host.h"
+#include "ui/views/animation/ink_drop_impl.h"
+#include "ui/views/animation/test/ink_drop_highlight_test_api.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/view.h"
+
+namespace {
+
+class TestButton : public views::Button {
+ public:
+  TestButton() : Button(views::Button::PressedCallback()) {}
+  TestButton(const TestButton&) = delete;
+  TestButton& operator=(const TestButton&) = delete;
+  ~TestButton() override = default;
+};
+
+}  // namespace
+
+class ToolbarInkDropUtilTest : public ChromeViewsTestBase,
+                               public ::testing::WithParamInterface<bool> {
+ public:
+  ToolbarInkDropUtilTest() = default;
+
+  void SetUp() override {
+    ChromeViewsTestBase::SetUp();
+
+    // Enable or disable the feature based on the test parameter
+    if (GetParam()) {
+      feature_list_.InitAndEnableFeature(features::kChromeRefresh2023);
+    } else {
+      feature_list_.InitAndDisableFeature(features::kChromeRefresh2023);
+    }
+
+    button_host_ = std::make_unique<TestButton>();
+  }
+
+  void TearDown() override { ChromeViewsTestBase::TearDown(); }
+
+  ~ToolbarInkDropUtilTest() override = default;
+
+ protected:
+  std::unique_ptr<TestButton> button_host_;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Basic test to check for various inkdrop properties for toolbar buttons.
+TEST_P(ToolbarInkDropUtilTest, ConfigureInkDropForToolbarTest) {
+  ConfigureInkDropForToolbar(button_host_.get());
+
+  if (!features::IsChromeRefresh2023()) {
+    EXPECT_TRUE(button_host_->GetHasInkDropActionOnClick());
+    EXPECT_EQ(views::InkDrop::Get(button_host_.get())->GetMode(),
+              views::InkDropHost::InkDropMode::ON);
+    EXPECT_EQ(views::InkDrop::Get(button_host_.get())->GetVisibleOpacity(),
+              kToolbarInkDropVisibleOpacity);
+  } else {
+    EXPECT_EQ(views::InkDrop::Get(button_host_.get())->GetLayerRegion(),
+              views::LayerRegion::kAbove);
+    std::unique_ptr<views::InkDropHighlight> highlight =
+        views::InkDrop::Get(button_host_.get())->CreateInkDropHighlight();
+    EXPECT_NE(highlight, nullptr);
+  }
+}
+
+// Parameterized test cases. The parameter is whether the feature is enabled.
+INSTANTIATE_TEST_SUITE_P(ChromeRefresh2023OnOff,
+                         ToolbarInkDropUtilTest,
+                         testing::Bool());
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index e67d127..1f92f101 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -167,6 +167,8 @@
 constexpr int kToolbarDividerHeight = 16;
 constexpr int kToolbarDividerCornerRadius = 1;
 constexpr int kToolbarDividerSpacing = 9;
+constexpr int kBrowserAppMenuRefreshExpandedMargin = 5;
+constexpr int kBrowserAppMenuRefreshCollapsedMargin = 2;
 
 }  // namespace
 
@@ -814,10 +816,26 @@
 void ToolbarView::LayoutCommon() {
   DCHECK(display_mode_ == DisplayMode::NORMAL);
 
-  const gfx::Insets interior_margin =
+  gfx::Insets interior_margin =
       GetLayoutInsets(browser_view_->webui_tab_strip()
                           ? LayoutInset::WEBUI_TAB_STRIP_TOOLBAR_INTERIOR_MARGIN
                           : LayoutInset::TOOLBAR_INTERIOR_MARGIN);
+
+  if (features::IsChromeRefresh2023() && !browser_view_->webui_tab_strip()) {
+    if (app_menu_button_->IsLabelPresentAndVisible()) {
+      // The interior margin in an expanded state should be more than in a
+      // collapsed state.
+      interior_margin.set_right(interior_margin.right() + 1);
+      app_menu_button_->SetProperty(
+          views::kMarginsKey,
+          gfx::Insets::VH(0, kBrowserAppMenuRefreshExpandedMargin));
+    } else {
+      app_menu_button_->SetProperty(
+          views::kMarginsKey,
+          gfx::Insets::VH(0, kBrowserAppMenuRefreshCollapsedMargin));
+    }
+  }
+
   layout_manager_->SetInteriorMargin(interior_margin);
 
   // Extend buttons to the window edge if we're either in a maximized or
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 6b5dd3b..f43ef9ac 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
@@ -72,6 +72,7 @@
 const char kSideSearchTutorialMetricPrefix[] = "SideSearch";
 constexpr char kTabGroupHeaderElementName[] = "TabGroupHeader";
 constexpr char kReadingListItemElementName[] = "ReadingListItem";
+constexpr char kChromeThemeBackElementName[] = "ChromeThemeBackElement";
 
 class BrowserHelpBubbleDelegate : public user_education::HelpBubbleDelegate {
  public:
@@ -688,10 +689,22 @@
         HelpBubbleArrow::kRightCenter, ui::CustomElementEventType(),
         /* must_remain_visible =*/false,
         /* transition_only_on_event =*/false,
-        user_education::TutorialDescription::NameElementsCallback(),
+        base::BindRepeating(
+            [](ui::InteractionSequence* sequence, ui::TrackedElement* element) {
+              sequence->NameElement(
+                  element, base::StringPiece(kChromeThemeBackElementName));
+              return true;
+            }),
         TutorialDescription::ContextMode::kAny);
     customize_chrome_description.steps.emplace_back(back_button_step);
 
+    // Hidden step - back button
+    TutorialDescription::Step back_button_hidden_step(
+        0, 0, ui::InteractionSequence::StepType::kHidden,
+        ui::ElementIdentifier(), kChromeThemeBackElementName,
+        HelpBubbleArrow::kNone);
+    customize_chrome_description.steps.emplace_back(back_button_hidden_step);
+
     // Completion of the tutorial.
     TutorialDescription::Step success_step(
         IDS_TUTORIAL_GENERIC_SUCCESS_TITLE,
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
index 8e64bc2..9d2168c 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
@@ -571,8 +571,7 @@
     const std::u16string& top_frame_for_display,
     const absl::optional<std::u16string>& iframe_for_display,
     const std::u16string& idp_for_display,
-    const content::IdentityProviderMetadata& idp_metadata,
-    IdentityRegistryCallback identity_registry_callback) {
+    const content::IdentityProviderMetadata& idp_metadata) {
   int subtitleLeftPadding = 2 * kLeftRightPadding;
   if (header_icon_view_) {
     ConfigureIdpBrandImageView(header_icon_view_, idp_metadata);
@@ -598,11 +597,9 @@
   row->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
       gfx::Insets::VH(kTopBottomPadding, kLeftRightPadding)));
-  identity_registry_callback_ = std::move(identity_registry_callback);
   auto button = std::make_unique<ContinueButton>(
-      base::BindRepeating(&Observer::ShowModalDialog,
-                          base::Unretained(observer_),
-                          idp_metadata.idp_signin_url),
+      base::BindRepeating(&Observer::OnSigninToIdP,
+                          base::Unretained(observer_)),
       l10n_util::GetStringUTF16(IDS_IDP_SIGNIN_STATUS_FAILURE_DIALOG_CONTINUE),
       this, idp_metadata);
   signin_to_idp_button_ = row->AddChildView(std::move(button));
@@ -634,15 +631,6 @@
   return base::UTF16ToUTF8(subtitle_label_->GetText());
 }
 
-bool AccountSelectionBubbleView::HasIdentityRegistryCallback() {
-  return !identity_registry_callback_.is_null();
-}
-
-IdentityRegistryCallback
-AccountSelectionBubbleView::GetIdentityRegistryCallback() {
-  return std::move(identity_registry_callback_);
-}
-
 gfx::Rect AccountSelectionBubbleView::GetBubbleBounds() {
   // The bubble initially looks like this relative to the contents_web_view:
   //                        |--------|
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.h b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
index 65636b8c..d0947f19 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.h
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
@@ -17,9 +17,6 @@
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/view.h"
 
-using IdentityRegistryCallback =
-    content::IdentityRequestDialogController::IdentityRegistryCallback;
-
 namespace views {
 class Checkbox;
 class ImageButton;
@@ -64,8 +61,9 @@
     // Called when the user clicks "close" button.
     virtual void OnCloseButtonClicked(const ui::Event& event) = 0;
 
-    // Called to load a modal webview.
-    virtual void ShowModalDialog(const GURL& url) = 0;
+    // Called when the user clicks the "continue" button on the sign-in
+    // failure dialog.
+    virtual void OnSigninToIdP() = 0;
 
     // Called when IdentityProvider.close() is called from the renderer.
     virtual void CloseModalDialog() = 0;
@@ -101,8 +99,7 @@
       const std::u16string& top_frame_for_display,
       const absl::optional<std::u16string>& iframe_for_display,
       const std::u16string& idp_for_display,
-      const content::IdentityProviderMetadata& idp_metadata,
-      IdentityRegistryCallback identity_registry_callback) override;
+      const content::IdentityProviderMetadata& idp_metadata) override;
 
   // Populates `idp_images` when an IDP image has been fetched.
   void AddIdpImage(const GURL& image_url, gfx::ImageSkia idp_image);
@@ -110,9 +107,6 @@
   std::string GetDialogTitle() const override;
   absl::optional<std::string> GetDialogSubtitle() const override;
 
-  bool HasIdentityRegistryCallback() override;
-  IdentityRegistryCallback GetIdentityRegistryCallback() override;
-
  private:
   gfx::Rect GetBubbleBounds() override;
 
@@ -225,9 +219,6 @@
   // Dangling when running Chromedriver's run_py_tests.py test suite.
   raw_ptr<Observer, DanglingUntriaged> observer_{nullptr};
 
-  // Callback to create an identity registry.
-  IdentityRegistryCallback identity_registry_callback_;
-
   // Used to ensure that callbacks are not run if the AccountSelectionBubbleView
   // is destroyed.
   base::WeakPtrFactory<AccountSelectionBubbleView> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view_interface.h b/chrome/browser/ui/views/webid/account_selection_bubble_view_interface.h
index 8bc69b6b..a60f400 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view_interface.h
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view_interface.h
@@ -10,9 +10,6 @@
 
 #include "chrome/browser/ui/views/webid/identity_provider_display_data.h"
 
-using IdentityRegistryCallback =
-    content::IdentityRequestDialogController::IdentityRegistryCallback;
-
 namespace content {
 struct IdentityRequestAccount;
 }  // namespace content
@@ -46,14 +43,10 @@
       const std::u16string& top_frame_for_display,
       const absl::optional<std::u16string>& iframe_for_display,
       const std::u16string& idp_for_display,
-      const content::IdentityProviderMetadata& idp_metadata,
-      IdentityRegistryCallback identity_registry_callback) = 0;
+      const content::IdentityProviderMetadata& idp_metadata) = 0;
 
   virtual std::string GetDialogTitle() const = 0;
   virtual absl::optional<std::string> GetDialogSubtitle() const = 0;
-
-  virtual bool HasIdentityRegistryCallback() = 0;
-  virtual IdentityRegistryCallback GetIdentityRegistryCallback() = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_WEBID_ACCOUNT_SELECTION_BUBBLE_VIEW_INTERFACE_H_
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc b/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc
index fab5e6a..a59d565 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc
@@ -365,8 +365,7 @@
         expected_subtitle.has_value()
             ? absl::make_optional<std::u16string>(kIframeETLDPlusOne)
             : absl::nullopt,
-        kIdpETLDPlusOne, content::IdentityProviderMetadata(),
-        /*identity_registry_callback=*/base::DoNothing());
+        kIdpETLDPlusOne, content::IdentityProviderMetadata());
 
     const std::vector<views::View*> children = dialog()->children();
     ASSERT_EQ(children.size(), 2u);
diff --git a/chrome/browser/ui/views/webid/fake_delegate.h b/chrome/browser/ui/views/webid/fake_delegate.h
index 95580f6..c7cbbf2 100644
--- a/chrome/browser/ui/views/webid/fake_delegate.h
+++ b/chrome/browser/ui/views/webid/fake_delegate.h
@@ -21,6 +21,7 @@
 
   void OnDismiss(content::IdentityRequestDialogController::DismissReason
                      dismiss_reason) override {}
+  void OnSigninToIdP() override {}
 
   // AccountSelectionView::Delegate
   gfx::NativeView GetNativeView() override;
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index 575da5e..a2f405b 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -151,8 +151,7 @@
     const std::string& top_frame_etld_plus_one,
     const absl::optional<std::string>& iframe_etld_plus_one,
     const std::string& idp_etld_plus_one,
-    const content::IdentityProviderMetadata& idp_metadata,
-    IdentityRegistryCallback identity_registry_callback) {
+    const content::IdentityProviderMetadata& idp_metadata) {
   state_ = State::IDP_SIGNIN_STATUS_MISMATCH;
   absl::optional<std::u16string> iframe_etld_plus_one_u16 =
       iframe_etld_plus_one ? absl::make_optional<std::u16string>(
@@ -180,8 +179,7 @@
 
   GetBubbleView()->ShowFailureDialog(
       base::UTF8ToUTF16(top_frame_etld_plus_one), iframe_etld_plus_one_u16,
-      base::UTF8ToUTF16(idp_etld_plus_one), idp_metadata,
-      std::move(identity_registry_callback));
+      base::UTF8ToUTF16(idp_etld_plus_one), idp_metadata);
 
   if (create_bubble) {
     bubble_widget_->Show();
@@ -373,16 +371,17 @@
       views::Widget::ClosedReason::kCloseButtonClicked);
 }
 
-void FedCmAccountSelectionView::ShowModalDialog(const GURL& url) {
+void FedCmAccountSelectionView::OnSigninToIdP() {
+  delegate_->OnSigninToIdP();
+}
+
+content::WebContents* FedCmAccountSelectionView::ShowModalDialog(
+    const GURL& url) {
   idp_signin_modal_dialog_ = FedCmModalDialogView::ShowFedCmModalDialog(
       delegate_->GetWebContents(), url, this);
-  if (GetBubbleView()->HasIdentityRegistryCallback()) {
-    std::move(GetBubbleView()->GetIdentityRegistryCallback())
-        .Run(idp_signin_modal_dialog_->GetWebViewWebContents());
-  }
-
   input_protector_->VisibilityChanged(false);
   bubble_widget_->Hide();
+  return idp_signin_modal_dialog_->GetWebViewWebContents();
 }
 
 void FedCmAccountSelectionView::CloseModalDialog() {
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
index 15816ec..0793a03 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
@@ -59,8 +59,7 @@
       const std::string& top_frame_etld_plus_one,
       const absl::optional<std::string>& iframe_etld_plus_one,
       const std::string& idp_etld_plus_one,
-      const content::IdentityProviderMetadata& idp_metadata,
-      IdentityRegistryCallback identity_registry_callback) override;
+      const content::IdentityProviderMetadata& idp_metadata) override;
   std::string GetTitle() const override;
   absl::optional<std::string> GetSubtitle() const override;
 
@@ -131,7 +130,8 @@
                      const ui::Event& event) override;
   void OnBackButtonClicked() override;
   void OnCloseButtonClicked(const ui::Event& event) override;
-  void ShowModalDialog(const GURL& url) override;
+  content::WebContents* ShowModalDialog(const GURL& url) override;
+  void OnSigninToIdP() override;
   void CloseModalDialog() override;
 
   void ShowVerifyingSheet(const Account& account,
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
index af79a0d1..9038d13f 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
@@ -77,8 +77,7 @@
       const std::u16string& top_frame_for_display,
       const absl::optional<std::u16string>& iframe_for_display,
       const std::u16string& idp_for_display,
-      const content::IdentityProviderMetadata& idp_metadata,
-      IdentityRegistryCallback identity_registry_callback) override {
+      const content::IdentityProviderMetadata& idp_metadata) override {
     sheet_type_ = SheetType::kFailure;
     account_ids_ = {};
   }
@@ -88,11 +87,6 @@
     return absl::nullopt;
   }
 
-  bool HasIdentityRegistryCallback() override { return false; }
-  IdentityRegistryCallback GetIdentityRegistryCallback() override {
-    return base::DoNothing();
-  }
-
   bool show_back_button_{false};
   absl::optional<SheetType> sheet_type_;
   std::vector<std::string> account_ids_;
@@ -156,6 +150,7 @@
                          const content::IdentityRequestAccount&) override {}
   void OnDismiss(
       content::IdentityRequestDialogController::DismissReason) override {}
+  void OnSigninToIdP() override {}
   gfx::NativeView GetNativeView() override { return gfx::NativeView(); }
 
   content::WebContents* GetWebContents() override { return web_contents_; }
@@ -329,10 +324,9 @@
   AccountSelectionBubbleView::Observer* observer =
       static_cast<AccountSelectionBubbleView::Observer*>(controller.get());
 
-  controller->ShowFailureDialog(
-      kTopFrameEtldPlusOne, kIframeEtldPlusOne, kIdpEtldPlusOne,
-      content::IdentityProviderMetadata(),
-      /*identity_provider_callback=*/base::DoNothing());
+  controller->ShowFailureDialog(kTopFrameEtldPlusOne, kIframeEtldPlusOne,
+                                kIdpEtldPlusOne,
+                                content::IdentityProviderMetadata());
   EXPECT_EQ(TestBubbleView::SheetType::kFailure, bubble_view_->sheet_type_);
 
   const char kAccountId[] = "account_id";
@@ -361,10 +355,9 @@
   AccountSelectionBubbleView::Observer* observer =
       static_cast<AccountSelectionBubbleView::Observer*>(controller.get());
 
-  controller->ShowFailureDialog(
-      kTopFrameEtldPlusOne, kIframeEtldPlusOne, kIdpEtldPlusOne,
-      content::IdentityProviderMetadata(),
-      /*identity_provider_callback=*/base::DoNothing());
+  controller->ShowFailureDialog(kTopFrameEtldPlusOne, kIframeEtldPlusOne,
+                                kIdpEtldPlusOne,
+                                content::IdentityProviderMetadata());
   EXPECT_EQ(TestBubbleView::SheetType::kFailure, bubble_view_->sheet_type_);
 
   const char kAccountId[] = "account_id";
diff --git a/chrome/browser/ui/web_applications/lacros_web_app_shelf_browsertest.cc b/chrome/browser/ui/web_applications/lacros_web_app_shelf_browsertest.cc
index de429c1..07ea699e 100644
--- a/chrome/browser/ui/web_applications/lacros_web_app_shelf_browsertest.cc
+++ b/chrome/browser/ui/web_applications/lacros_web_app_shelf_browsertest.cc
@@ -206,13 +206,14 @@
   ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
       app_id, static_cast<uint32_t>(ShelfItemState::kActive)));
 
-  ASSERT_TRUE(content::ExecuteScript(web_contents, "navigator.setAppBadge();"));
+  ASSERT_TRUE(content::ExecJs(web_contents, "navigator.setAppBadge();",
+                              content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
   ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
       app_id, static_cast<uint32_t>(ShelfItemState::kActive) |
                   static_cast<uint32_t>(ShelfItemState::kNotification)));
 
-  ASSERT_TRUE(
-      content::ExecuteScript(web_contents, "navigator.clearAppBadge();"));
+  ASSERT_TRUE(content::ExecJs(web_contents, "navigator.clearAppBadge();",
+                              content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
   ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
       app_id, static_cast<uint32_t>(ShelfItemState::kActive)));
 
diff --git a/chrome/browser/ui/web_applications/web_app_badging_browsertest.cc b/chrome/browser/ui/web_applications/web_app_badging_browsertest.cc
index ed6cd25e..34e4c71 100644
--- a/chrome/browser/ui/web_applications/web_app_badging_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_badging_browsertest.cc
@@ -24,6 +24,7 @@
 #include "content/public/browser/browsing_data_filter_builder.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 
 using content::RenderFrameHost;
 
@@ -194,7 +195,7 @@
     awaiter_ = std::make_unique<base::RunLoop>();
     delegate_->ResetBadges();
 
-    ASSERT_TRUE(content::ExecuteScript(on, script));
+    ASSERT_TRUE(content::ExecJs(on, script));
 
     if (badge_change_map_.size() >= expected_badge_change_count_)
       return;
@@ -490,10 +491,10 @@
                                          ->GetActiveWebContents()
                                          ->GetPrimaryMainFrame();
 
-  ASSERT_TRUE(
-      content::ExecuteScript(incognito_frame, "navigator.setAppBadge()"));
-  ASSERT_TRUE(
-      content::ExecuteScript(incognito_frame, "navigator.clearAppBadge()"));
+  ASSERT_TRUE(content::ExecJs(incognito_frame, "navigator.setAppBadge()",
+                              content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
+  ASSERT_TRUE(content::ExecJs(incognito_frame, "navigator.clearAppBadge()",
+                              content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
 
   // Updating badges through a ServiceWorkerGlobalScope must not crash.
   const std::string register_app_service_worker_script = content::JsReplace(
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index d7417bb..129f7bb 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -380,7 +380,7 @@
   // Changing background color should update the toolbar color.
   {
     content::BackgroundColorChangeWaiter waiter(web_contents);
-    EXPECT_TRUE(content::ExecuteScript(
+    EXPECT_TRUE(content::ExecJs(
         web_contents, "document.body.style.backgroundColor = 'cyan';"));
     waiter.Wait();
     EXPECT_EQ(app_browser->app_controller()->GetBackgroundColor().value(),
@@ -486,7 +486,7 @@
   // colors.
   {
     content::BackgroundColorChangeWaiter waiter(web_contents);
-    EXPECT_TRUE(content::ExecuteScript(
+    EXPECT_TRUE(content::ExecJs(
         web_contents, "document.body.style.backgroundColor = 'cyan';"));
     waiter.Wait();
     EXPECT_EQ(app_browser->app_controller()->GetBackgroundColor().value(),
@@ -1795,7 +1795,7 @@
       app_browser->tab_strip_model()
           ->GetActiveWebContents()
           ->GetPrimaryMainFrame();
-  EXPECT_TRUE(content::ExecuteScript(
+  EXPECT_TRUE(content::ExecJs(
       render_frame_host,
       "navigator.geolocation.getCurrentPosition(function(){});"));
 }
diff --git a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
index e82aff87..b7839de 100644
--- a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
@@ -115,8 +115,7 @@
     content::WebContents* contents,
     const GURL& url) {
   content::WebContentsAddedObserver tab_added_observer;
-  EXPECT_TRUE(
-      content::ExecuteScript(contents, "window.open('" + url.spec() + "');"));
+  EXPECT_TRUE(content::ExecJs(contents, "window.open('" + url.spec() + "');"));
   content::WebContents* new_contents = tab_added_observer.GetWebContents();
   EXPECT_TRUE(new_contents);
   WaitForLoadStop(new_contents);
@@ -134,8 +133,8 @@
 bool WebAppControllerBrowserTest::NavigateInRenderer(
     content::WebContents* contents,
     const GURL& url) {
-  EXPECT_TRUE(content::ExecuteScript(
-      contents, "window.location = '" + url.spec() + "';"));
+  EXPECT_TRUE(
+      content::ExecJs(contents, "window.location = '" + url.spec() + "';"));
   bool success = content::WaitForLoadStop(contents);
   EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL());
   return success;
diff --git a/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc b/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc
index fceb809..7b50799 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc
@@ -453,7 +453,14 @@
                                       ClientMode::kAuto, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(WebAppLaunchHandlerBrowserTest, SelectActiveBrowser) {
+// https://crbug.com/1444959
+#if BUILDFLAG(IS_LINUX)
+#define MAYBE_SelectActiveBrowser DISABLED_SelectActiveBrowser
+#else
+#define MAYBE_SelectActiveBrowser SelectActiveBrowser
+#endif
+IN_PROC_BROWSER_TEST_F(WebAppLaunchHandlerBrowserTest,
+                       MAYBE_SelectActiveBrowser) {
   AppId app_id =
       InstallTestWebApp("/web_apps/basic.html", /*await_metric=*/false);
   EXPECT_EQ(GetLaunchHandler(app_id), absl::nullopt);
diff --git a/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc b/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc
index a619fda..8fc55217 100644
--- a/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc
@@ -236,7 +236,7 @@
   // Navigate an about:blank page using JavaScript.
   BrowserChangeObserver added_observer(
       nullptr, BrowserChangeObserver::ChangeType::kAdded);
-  ASSERT_TRUE(content::ExecuteScript(
+  ASSERT_TRUE(content::ExecJs(
       browser()->tab_strip_model()->GetActiveWebContents(),
       base::StringPrintf("location = '%s';", in_scope_1_.spec().c_str())));
   Browser* app_browser = added_observer.Wait();
diff --git a/chrome/browser/ui/web_applications/web_app_metrics.cc b/chrome/browser/ui/web_applications/web_app_metrics.cc
index 2852f22..09d04946 100644
--- a/chrome/browser/ui/web_applications/web_app_metrics.cc
+++ b/chrome/browser/ui/web_applications/web_app_metrics.cc
@@ -4,10 +4,7 @@
 
 #include "chrome/browser/ui/web_applications/web_app_metrics.h"
 
-#include <stdint.h>
-#include <algorithm>
 #include <string>
-#include <utility>
 #include <vector>
 
 #include "base/check.h"
@@ -22,18 +19,14 @@
 #include "base/one_shot_event.h"
 #include "base/power_monitor/power_monitor.h"
 #include "base/ranges/algorithm.h"
-#include "base/strings/string_piece_forward.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
-#include "base/types/pass_key.h"
-#include "base/value_iterators.h"
-#include "base/values.h"
+#include "build/build_config.h"
 #include "chrome/browser/after_startup_task_utils.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -46,15 +39,13 @@
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
 #include "chrome/browser/web_applications/web_app_ui_manager.h"
 #include "chrome/common/chrome_features.h"
+#include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
 #include "components/site_engagement/content/engagement_type.h"
 #include "components/site_engagement/content/site_engagement_service.h"
-#include "components/sync/base/model_type.h"
 #include "components/webapps/browser/banners/app_banner_manager.h"
 #include "content/public/browser/web_contents.h"
-#include "services/metrics/public/cpp/ukm_recorder.h"
-#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
+#include "third_party/blink/public/mojom/manifest/display_mode.mojom-forward.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/web_applications/preinstalled_web_app_window_experiment_utils.h"
@@ -63,10 +54,6 @@
 using DisplayMode = blink::mojom::DisplayMode;
 using content::WebContents;
 
-namespace syncer {
-class SyncService;
-}  // namespace syncer
-
 namespace web_app {
 
 namespace {
@@ -457,8 +444,7 @@
   }
   last_recorded_web_app_start_url_ = features.start_url;
 
-  FlushOldRecordsAndUpdate(features, profile_,
-                           SyncServiceFactory::GetForProfile(profile_));
+  FlushOldRecordsAndUpdate(features, profile_);
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_metrics_browsertest.cc b/chrome/browser/ui/web_applications/web_app_metrics_browsertest.cc
index 691e0a3c..b75823e 100644
--- a/chrome/browser/ui/web_applications/web_app_metrics_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_metrics_browsertest.cc
@@ -12,14 +12,12 @@
 #include "base/numerics/clamped_math.h"
 #include "base/run_loop.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
-#include "base/test/bind.h"
 #include "base/time/time.h"
 #include "base/time/time_override.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/intent_helper/preferred_apps_test_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
@@ -32,11 +30,6 @@
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/common/chrome_features.h"
-#include "components/keyed_service/core/keyed_service.h"
-#include "components/services/app_service/public/cpp/app_types.h"
-#include "components/sync/driver/sync_service.h"
-#include "components/sync/driver/sync_user_settings.h"
-#include "components/sync/test/test_sync_service.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "content/public/test/browser_test.h"
@@ -58,10 +51,6 @@
 #include "chrome/browser/web_applications/app_service/test/loopback_crosapi_app_service_proxy.h"
 #endif
 
-namespace content {
-class BrowserContext;
-}
-
 namespace web_app {
 
 using UkmEntry = ukm::builders::WebApp_DailyInteraction;
@@ -80,16 +69,6 @@
   WebAppMetricsBrowserTest& operator=(const WebAppMetricsBrowserTest&) = delete;
   ~WebAppMetricsBrowserTest() override = default;
 
-  void OnWillCreateBrowserContextServices(
-      content::BrowserContext* context) override {
-    SyncServiceFactory::GetInstance()->SetTestingFactory(
-        context,
-        base::BindLambdaForTesting(
-            [](content::BrowserContext*) -> std::unique_ptr<KeyedService> {
-              return std::make_unique<syncer::TestSyncService>();
-            }));
-  }
-
   void SetUpOnMainThread() override {
     WebAppControllerBrowserTest::SetUpOnMainThread();
     // Ignore real window activation which causes flakiness in tests.
@@ -110,8 +89,7 @@
   }
 
   void ForceEmitMetricsNow() {
-    FlushAllRecordsForTesting(profile(),
-                              SyncServiceFactory::GetForProfile(profile()));
+    FlushAllRecordsForTesting(profile());
     // Ensure async call for origin check in daily_metrics_helper completes.
     base::ThreadPoolInstance::Get()->FlushForTesting();
     base::RunLoop().RunUntilIdle();
@@ -155,27 +133,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppMetricsBrowserTest,
-                       AppSyncNotEnabled_RecordsNothing) {
-  ukm::TestAutoSetUkmRecorder ukm_recorder;
-  SyncServiceFactory::GetForProfile(profile())
-      ->GetUserSettings()
-      ->SetSelectedTypes(/*sync_everything=*/false, /*types=*/{});
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  SyncServiceFactory::GetForProfile(profile())
-      ->GetUserSettings()
-      ->SetSelectedOsTypes(/*sync_all_os_types=*/false, /*types=*/{});
-#endif
-
-  AddBlankTabAndShow(browser());
-  NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL());
-
-  ForceEmitMetricsNow();
-
-  auto entries = ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
-  ASSERT_EQ(entries.size(), 0U);
-}
-
-IN_PROC_BROWSER_TEST_F(WebAppMetricsBrowserTest,
                        InstalledWebAppInTab_RecordsDailyInteraction) {
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
diff --git a/chrome/browser/ui/webid/account_selection_view.h b/chrome/browser/ui/webid/account_selection_view.h
index 109c40e..88da770 100644
--- a/chrome/browser/ui/webid/account_selection_view.h
+++ b/chrome/browser/ui/webid/account_selection_view.h
@@ -12,8 +12,6 @@
 #include "ui/gfx/native_widget_types.h"
 
 using Account = content::IdentityRequestAccount;
-using IdentityRegistryCallback =
-    content::IdentityRequestDialogController::IdentityRegistryCallback;
 
 // This class represents the interface used for communicating between the
 // identity dialog controller with the Android frontend.
@@ -30,6 +28,7 @@
     virtual void OnDismiss(
         content::IdentityRequestDialogController::DismissReason
             dismiss_reason) = 0;
+    virtual void OnSigninToIdP() = 0;
     // The web page view containing the focused field.
     virtual gfx::NativeView GetNativeView() = 0;
     // The WebContents for the page.
@@ -75,13 +74,12 @@
       const std::string& top_frame_for_display,
       const absl::optional<std::string>& iframe_for_display,
       const std::string& idp_for_display,
-      const content::IdentityProviderMetadata& idp_metadata,
-      IdentityRegistryCallback identity_registry_callback) = 0;
+      const content::IdentityProviderMetadata& idp_metadata) = 0;
 
   virtual std::string GetTitle() const = 0;
   virtual absl::optional<std::string> GetSubtitle() const = 0;
 
-  virtual void ShowModalDialog(const GURL& url) = 0;
+  virtual content::WebContents* ShowModalDialog(const GURL& url) = 0;
   virtual void CloseModalDialog() = 0;
 
  protected:
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.cc b/chrome/browser/ui/webid/identity_dialog_controller.cc
index 06ca123..63494fc 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.cc
+++ b/chrome/browser/ui/webid/identity_dialog_controller.cc
@@ -48,10 +48,11 @@
     const std::string& idp_for_display,
     const content::IdentityProviderMetadata& idp_metadata,
     DismissCallback dismiss_callback,
-    IdentityRegistryCallback identity_registry_callback) {
+    SigninToIdPCallback signin_callback) {
   const GURL rp_url = rp_web_contents->GetLastCommittedURL();
   rp_web_contents_ = rp_web_contents;
   on_dismiss_ = std::move(dismiss_callback);
+  on_signin_ = std::move(signin_callback);
   if (!account_view_)
     account_view_ = AccountSelectionView::Create(this);
   // Else:
@@ -59,8 +60,11 @@
   //   sign-in attempt failed.
 
   account_view_->ShowFailureDialog(top_frame_for_display, iframe_for_display,
-                                   idp_for_display, idp_metadata,
-                                   std::move(identity_registry_callback));
+                                   idp_for_display, idp_metadata);
+}
+
+void IdentityDialogController::OnSigninToIdP() {
+  std::move(on_signin_).Run();
 }
 
 void IdentityDialogController::ShowIdpSigninFailureDialog(
@@ -102,11 +106,12 @@
   return rp_web_contents_;
 }
 
-void IdentityDialogController::ShowModalDialog(
+content::WebContents* IdentityDialogController::ShowModalDialog(
     const GURL& url,
-    TokenCallback on_resolve,
     DismissCallback dismiss_callback) {
-  account_view_->ShowModalDialog(url);
+  // TODO(crbug.com/1429083): connect the dimiss_callback to the
+  // modal dialog close button.
+  return account_view_->ShowModalDialog(url);
 }
 
 void IdentityDialogController::CloseModalDialog() {
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.h b/chrome/browser/ui/webid/identity_dialog_controller.h
index 8113b1b..1bba4c8 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.h
+++ b/chrome/browser/ui/webid/identity_dialog_controller.h
@@ -19,8 +19,6 @@
     content::IdentityRequestDialogController::AccountSelectionCallback;
 using DismissCallback =
     content::IdentityRequestDialogController::DismissCallback;
-using IdentityRegistryCallback =
-    content::IdentityRequestDialogController::IdentityRegistryCallback;
 
 // The IdentityDialogController controls the views that are used across
 // browser-mediated federated sign-in flows.
@@ -49,29 +47,29 @@
       bool show_auto_reauthn_checkbox,
       AccountSelectionCallback on_selected,
       DismissCallback dismiss_callback) override;
-  void ShowFailureDialog(
-      content::WebContents* rp_web_contents,
-      const std::string& top_frame_for_display,
-      const absl::optional<std::string>& iframe_for_display,
-      const std::string& idp_for_display,
-      const content::IdentityProviderMetadata& idp_metadata,
-      DismissCallback dismiss_callback,
-      IdentityRegistryCallback identity_registry_callback) override;
+  void ShowFailureDialog(content::WebContents* rp_web_contents,
+                         const std::string& top_frame_for_display,
+                         const absl::optional<std::string>& iframe_for_display,
+                         const std::string& idp_for_display,
+                         const content::IdentityProviderMetadata& idp_metadata,
+                         DismissCallback dismiss_callback,
+                         SigninToIdPCallback signin_callback) override;
   void ShowIdpSigninFailureDialog(base::OnceClosure dismiss_callback) override;
 
   std::string GetTitle() const override;
   absl::optional<std::string> GetSubtitle() const override;
 
   // Show a modal dialog that loads content from the IdP in a WebView.
-  void ShowModalDialog(const GURL& url,
-                       TokenCallback on_resolve,
-                       DismissCallback dismiss_callback) override;
+  content::WebContents* ShowModalDialog(
+      const GURL& url,
+      DismissCallback dismiss_callback) override;
   void CloseModalDialog() override;
 
   // AccountSelectionView::Delegate:
   void OnAccountSelected(const GURL& idp_config_url,
                          const Account& account) override;
   void OnDismiss(DismissReason dismiss_reason) override;
+  void OnSigninToIdP() override;
   gfx::NativeView GetNativeView() override;
   content::WebContents* GetWebContents() override;
 
@@ -79,6 +77,7 @@
   std::unique_ptr<AccountSelectionView> account_view_{nullptr};
   AccountSelectionCallback on_account_selection_;
   DismissCallback on_dismiss_;
+  SigninToIdPCallback on_signin_;
   raw_ptr<content::WebContents> rp_web_contents_;
 };
 
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h b/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h
index 8dd55e0..96e3fd2 100644
--- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h
+++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_ACCESS_CODE_CAST_ACCESS_CODE_CAST_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_ACCESS_CODE_CAST_ACCESS_CODE_CAST_HANDLER_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h"
 #include "chrome/browser/media/router/discovery/access_code/discovery_resources.pb.h"
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h
index aa142bc..871be74 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ash/file_manager/file_tasks.h"
 #include "chrome/browser/ash/file_system_provider/mount_path_util.h"
diff --git a/chrome/browser/ui/webui/ash/login/core_oobe_handler.cc b/chrome/browser/ui/webui/ash/login/core_oobe_handler.cc
index 1c492f8..f94ad1a 100644
--- a/chrome/browser/ui/webui/ash/login/core_oobe_handler.cc
+++ b/chrome/browser/ui/webui/ash/login/core_oobe_handler.cc
@@ -135,6 +135,7 @@
   AddCallback("screenStateInitialize", &CoreOobeHandler::HandleInitialized);
   AddCallback("updateCurrentScreen",
               &CoreOobeHandler::HandleUpdateCurrentScreen);
+  AddCallback("backdropLoaded", &CoreOobeHandler::HandleBackdropLoaded);
   AddCallback("launchHelpApp", &CoreOobeHandler::HandleLaunchHelpApp);
   AddCallback("raiseTabKeyEvent", &CoreOobeHandler::HandleRaiseTabKeyEvent);
 
@@ -168,6 +169,10 @@
   GetOobeUI()->CurrentScreenChanged(screen);
 }
 
+void CoreOobeHandler::HandleBackdropLoaded() {
+  // TODO (b/269104621) Event picked up by b/269104621
+}
+
 void CoreOobeHandler::HandleEnableShelfButtons(bool enable) {
   if (LoginDisplayHost::default_host())
     LoginDisplayHost::default_host()->SetShelfButtonsEnabled(enable);
diff --git a/chrome/browser/ui/webui/ash/login/core_oobe_handler.h b/chrome/browser/ui/webui/ash/login/core_oobe_handler.h
index df34784..3844ff9 100644
--- a/chrome/browser/ui/webui/ash/login/core_oobe_handler.h
+++ b/chrome/browser/ui/webui/ash/login/core_oobe_handler.h
@@ -100,6 +100,7 @@
   void HandleEnableShelfButtons(bool enable);
   void HandleInitialized();
   void HandleUpdateCurrentScreen(const std::string& screen);
+  void HandleBackdropLoaded();
   void HandleLaunchHelpApp(int help_topic_id);
   // Handles demo mode setup for tests. Accepts 'online' and 'offline' as
   // `demo_config`.
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 515bfa4..f67172ec 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -1263,6 +1263,8 @@
         GURL(chrome::kOsUIHelpAppURL), GURL(chrome::kOsUINetExportURL),
         GURL(chrome::kOsUILauncherInternalsURL),
         GURL(chrome::kOsUIExtensionsInternalsURL),
+        GURL(chrome::kChromeUINotificationTesterURL),
+        GURL(chrome::kOsUINotificationTesterURL),
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
         // IME extension's Japanese options page. Opened via OS_URL_HANDLER SWA
diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler_unittest.cc b/chrome/browser/ui/webui/history_clusters/history_clusters_handler_unittest.cc
index eef6cdd..a51adc6b 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler_unittest.cc
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler_unittest.cc
@@ -10,12 +10,18 @@
 
 #include "base/test/bind.h"
 #include "base/time/time.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/history_clusters/history_clusters_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/tabs/tab_group.h"
+#include "chrome/browser/ui/tabs/tab_group_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/url_row.h"
 #include "components/history_clusters/core/history_clusters_types.h"
-#include "content/public/test/browser_task_environment.h"
+#include "components/history_clusters/public/mojom/history_cluster_types.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -36,18 +42,42 @@
   return visit;
 }
 
-class HistoryClustersHandlerTest : public testing::Test {
+class HistoryClustersHandlerTest : public BrowserWithTestWindowTest {
  public:
-  HistoryClustersHandlerTest() {
-    TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-        &profile_,
-        base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+  HistoryClustersHandlerTest() = default;
+
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+
+    web_contents_ = content::WebContents::Create(
+        content::WebContents::CreateParams(profile()));
+    handler_ = std::make_unique<HistoryClustersHandler>(
+        mojo::PendingReceiver<history_clusters::mojom::PageHandler>(),
+        profile(), web_contents_.get());
   }
 
-  // Everything must be called on Chrome_UIThread.
-  content::BrowserTaskEnvironment task_environment_;
+  void TearDown() override {
+    handler_.reset();
+    web_contents_.reset();
+    BrowserWithTestWindowTest::TearDown();
+  }
 
-  TestingProfile profile_;
+  HistoryClustersHandler& handler() { return *handler_; }
+
+ private:
+  // BrowserWithTestWindowTest:
+  TestingProfile::TestingFactories GetTestingFactories() override {
+    return {
+        {HistoryClustersServiceFactory::GetInstance(),
+         HistoryClustersServiceFactory::GetDefaultFactory()},
+        {HistoryServiceFactory::GetInstance(),
+         HistoryServiceFactory::GetDefaultFactory()},
+        {TemplateURLServiceFactory::GetInstance(),
+         base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)}};
+  }
+
+  std::unique_ptr<content::WebContents> web_contents_;
+  std::unique_ptr<HistoryClustersHandler> handler_;
 };
 
 // Just a basic test that we transform the data to mojom. A lot of the meat of
@@ -64,7 +94,7 @@
   clusters.push_back(cluster);
 
   mojom::QueryResultPtr mojom_result =
-      QueryClustersResultToMojom(&profile_, "query", clusters, true, false);
+      QueryClustersResultToMojom(profile(), "query", clusters, true, false);
 
   EXPECT_EQ(mojom_result->query, "query");
   EXPECT_EQ(mojom_result->can_load_more, true);
@@ -85,7 +115,30 @@
   EXPECT_EQ(cluster_mojom->related_searches[4]->query, "five");
 }
 
-// TODO(manukh) Add a test case for `VisitToMojom`.
+TEST_F(HistoryClustersHandlerTest, OpenVisitUrlsInTabGroup) {
+  std::vector<mojom::URLVisitPtr> visits;
+  visits.push_back(mojom::URLVisit::New());
+  visits.back()->normalized_url = GURL("http://www.google.com/search?q=foo");
+  visits.push_back(mojom::URLVisit::New());
+  visits.back()->normalized_url = GURL("http://foo/1");
+  handler().OpenVisitUrlsInTabGroup(std::move(visits), "My Tab Group Name");
+
+  TabStripModel* tab_strip_model = browser()->tab_strip_model();
+  ASSERT_EQ(2u, static_cast<size_t>(tab_strip_model->GetTabCount()));
+  ASSERT_EQ(GURL("http://www.google.com/search?q=foo"),
+            tab_strip_model->GetWebContentsAt(0)->GetURL());
+  ASSERT_EQ(GURL("http://foo/1"),
+            tab_strip_model->GetWebContentsAt(1)->GetURL());
+
+  TabGroupModel* tab_group_model = tab_strip_model->group_model();
+  ASSERT_EQ(1u, tab_group_model->ListTabGroups().size());
+  ASSERT_EQ(0, tab_strip_model->GetIndexOfWebContents(
+                   tab_strip_model->GetActiveWebContents()));
+  auto* tab_group =
+      tab_group_model->GetTabGroup(tab_group_model->ListTabGroups()[0]);
+  ASSERT_TRUE(tab_group);
+  EXPECT_EQ(tab_group->visual_data()->title(), u"My Tab Group Name");
+}
 
 }  // namespace
 
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler.h b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler.h
index 8d91713..d6ebd315 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler.h
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_PRIVACY_SANDBOX_PRIVACY_SANDBOX_DIALOG_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_PRIVACY_SANDBOX_PRIVACY_SANDBOX_DIALOG_HANDLER_H_
 
+#include "base/gtest_prod_util.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_message_handler.h"
diff --git a/chrome/browser/ui/webui/settings/ash/crostini_section.cc b/chrome/browser/ui/webui/settings/ash/crostini_section.cc
index 64178734..b155552 100644
--- a/chrome/browser/ui/webui/settings/ash/crostini_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/crostini_section.cc
@@ -447,14 +447,15 @@
     html_source->AddString("crostiniContainerUpgradeSubtext", "");
   }
 
-  // Should the crostini section in settings be displayed?
+  // Crostini section in settings is always displayed.
+  // Should we show that Crostini is supported?
   html_source->AddBoolean(
-      "showCrostini",
+      "isCrostiniSupported",
       crostini::CrostiniFeatures::Get()->CouldBeAllowed(profile_));
 
   // Should we actually enable the button to install it?
   html_source->AddBoolean(
-      "allowCrostini",
+      "isCrostiniAllowed",
       crostini::CrostiniFeatures::Get()->IsAllowedNow(profile_));
 
   // Should Bruschetta be displayed in the settings at all?
diff --git a/chrome/browser/ui/webui/settings/ash/device_section.cc b/chrome/browser/ui/webui/settings/ash/device_section.cc
index 7202e64..5cf60ee 100644
--- a/chrome/browser/ui/webui/settings/ash/device_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_section.cc
@@ -848,8 +848,7 @@
        IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_DISABLED},
       {"perDeviceKeyboardKeyEscape",
        IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_ESCAPE},
-      {"perDeviceKeyboardKeyExternalMeta",
-       IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_EXTERNAL_META},
+      {"perDeviceKeyboardKeyMeta", IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_META},
       {"perDeviceKeyboardKeySearch",
        IDS_SETTINGS_PER_DEVICE_KEYBOARD_KEY_SEARCH},
 
diff --git a/chrome/browser/ui/webui/settings/ash/parental_controls_handler.cc b/chrome/browser/ui/webui/settings/ash/parental_controls_handler.cc
index e752fb2..7ab482c 100644
--- a/chrome/browser/ui/webui/settings/ash/parental_controls_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/parental_controls_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/settings/ash/parental_controls_handler.h"
 
+#include "ash/public/cpp/new_window_delegate.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/values.h"
@@ -12,15 +13,11 @@
 #include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ash/child_accounts/child_user_service.h"
-#include "chrome/browser/ui/browser_navigator.h"
-#include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/webui/ash/add_supervision/add_supervision_ui.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
-#include "ui/base/page_transition_types.h"
-#include "ui/base/window_open_disposition.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/events/event_constants.h"
 #include "url/gurl.h"
@@ -85,11 +82,10 @@
   }
 
   // As a last resort, launch browser to the family link site.
-  NavigateParams params(profile_, GURL(kFamilyLinkSiteURL),
-                        ui::PAGE_TRANSITION_FROM_API);
-  params.disposition = WindowOpenDisposition::NEW_WINDOW;
-  params.window_action = NavigateParams::SHOW_WINDOW;
-  Navigate(&params);
+  NewWindowDelegate::GetPrimary()->OpenUrl(
+      GURL(kFamilyLinkSiteURL),
+      NewWindowDelegate::OpenUrlFrom::kUserInteraction,
+      NewWindowDelegate::Disposition::kNewWindow);
 }
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/privacy_section.cc b/chrome/browser/ui/webui/settings/ash/privacy_section.cc
index 8bb3dad1..8d590e3b 100644
--- a/chrome/browser/ui/webui/settings/ash/privacy_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/privacy_section.cc
@@ -402,6 +402,10 @@
        IDS_OS_SETTINGS_PRIVACY_HUB_MICROPHONE_TOGGLE_SUBTEXT},
       {"noMicrophoneConnectedText",
        IDS_OS_SETTINGS_PRIVACY_HUB_NO_MICROPHONE_CONNECTED_TEXT},
+      {"speakOnMuteDetectionToggleTitle",
+       IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_TITLE},
+      {"speakOnMuteDetectionToggleSubtext",
+       IDS_OS_SETTINGS_PRIVACY_HUB_SPEAK_ON_MUTE_DETECTION_TOGGLE_SUBTEXT},
       {"geolocationToggleTitle",
        IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_TOGGLE_TITLE},
       {"geolocationToggleDesc",
@@ -426,6 +430,8 @@
                           ash::features::IsCrosPrivacyHubV1Enabled());
   html_source->AddBoolean("showPrivacyHubFuturePage",
                           ash::features::IsCrosPrivacyHubV2Enabled());
+  html_source->AddBoolean("showSpeakOnMuteDetectionPage",
+                          ash::features::IsSpeakOnMuteEnabled());
 
   html_source->AddString(
       "smartPrivacyDesc",
@@ -443,6 +449,9 @@
   html_source->AddString("peripheralDataAccessLearnMoreURL",
                          chrome::kPeripheralDataAccessHelpURL);
 
+  html_source->AddString("speakOnMuteDetectionLearnMoreURL",
+                         chrome::kSpeakOnMuteDetectionLearnMoreURL);
+
   html_source->AddBoolean("showSecureDnsSetting", IsSecureDnsAvailable());
   html_source->AddBoolean("showSecureDnsOsSettingLink", false);
 
@@ -565,7 +574,8 @@
   RegisterNestedSettingBulk(
       mojom::Subpage::kPrivacyHub,
       {{mojom::Setting::kCameraOnOff, mojom::Setting::kMicrophoneOnOff,
-        mojom::Setting::kGeolocationOnOff}},
+        mojom::Setting::kGeolocationOnOff,
+        mojom::Setting::kSpeakOnMuteDetectionOnOff}},
       generator);
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
index 2d2012b0d..baa854f 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
@@ -216,6 +216,7 @@
   kMicrophoneOnOff = 1117,
   kGeolocationOnOff = 1118,
   kLockScreenNotification = 1119,
+  kSpeakOnMuteDetectionOnOff = 1120,
 
   // Languages and Input section.
   kAddLanguage = 1200,
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
index f1524c55..e4cfe91 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
diff --git a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc
index 35af997..38a49ad 100644
--- a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc
@@ -80,10 +80,11 @@
     auto* helper =
         companion::CompanionTabHelper::FromWebContents(active_web_contents);
     helper->SetCompanionPageHandler(weak_ptr_factory_.GetWeakPtr());
+    metrics_logger_->RecordOpenTrigger(
+        helper->GetAndResetMostRecentSidePanelOpenTrigger());
+
     std::string initial_text_query = helper->GetTextQuery();
     if (!initial_text_query.empty()) {
-      metrics_logger_->RecordOpenTrigger(
-          companion::OpenTrigger::kContextMenuTextSearch);
       OnSearchTextQuery(initial_text_query);
       return;
     }
@@ -91,13 +92,10 @@
     std::unique_ptr<side_panel::mojom::ImageQuery> image_query =
         helper->GetImageQuery();
     if (image_query) {
-      metrics_logger_->RecordOpenTrigger(
-          companion::OpenTrigger::kContextMenuImageSearch);
       OnImageQuery(*image_query);
       return;
     }
 
-    metrics_logger_->RecordOpenTrigger(companion::OpenTrigger::kOther);
     NotifyURLChanged(/*is_full_reload=*/true);
   }
 }
diff --git a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h
index dd81d2e..6ff86ed 100644
--- a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h
@@ -10,6 +10,7 @@
 #include "chrome/browser/companion/core/constants.h"
 #include "chrome/browser/companion/core/mojom/companion.mojom.h"
 #include "chrome/browser/companion/core/msbb_delegate.h"
+#include "chrome/browser/ui/side_panel/side_panel_enums.h"
 #include "components/lens/buildflags.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
index 596c7d2..810eca9 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
@@ -5,16 +5,16 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SIGNIN_DICE_WEB_SIGNIN_INTERCEPT_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_SIGNIN_DICE_WEB_SIGNIN_INTERCEPT_HANDLER_H_
 
-#include "content/public/browser/web_ui_message_handler.h"
-
 #include <string>
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 #include "base/values.h"
 #include "chrome/browser/signin/dice_web_signin_interceptor.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "content/public/browser/web_ui_message_handler.h"
 
 // WebUI message handler for the Dice web signin intercept bubble.
 class DiceWebSigninInterceptHandler : public content::WebUIMessageHandler,
diff --git a/chrome/browser/ui/webui/signin/profile_picker_ui.cc b/chrome/browser/ui/webui/signin/profile_picker_ui.cc
index 1f67b9f..4a08d1d 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_ui.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_ui.cc
@@ -281,20 +281,14 @@
       "isLocalProfileCreationDialogEnabled",
       base::FeatureList::IsEnabled(kSyncPromoAfterSigninIntercept));
 
-  html_source->AddBoolean(
-      "isTangibleSyncEnabled",
-      base::FeatureList::IsEnabled(switches::kTangibleSync));
-
-  html_source->AddResourcePath("images/tangible_sync_style_left_banner.svg",
+  html_source->AddResourcePath("images/left_banner.svg",
                                IDR_SIGNIN_IMAGES_SHARED_LEFT_BANNER_SVG);
-  html_source->AddResourcePath(
-      "images/tangible_sync_style_left_banner_dark.svg",
-      IDR_SIGNIN_IMAGES_SHARED_LEFT_BANNER_DARK_SVG);
-  html_source->AddResourcePath("images/tangible_sync_style_right_banner.svg",
+  html_source->AddResourcePath("images/left_banner_dark.svg",
+                               IDR_SIGNIN_IMAGES_SHARED_LEFT_BANNER_DARK_SVG);
+  html_source->AddResourcePath("images/right_banner.svg",
                                IDR_SIGNIN_IMAGES_SHARED_RIGHT_BANNER_SVG);
-  html_source->AddResourcePath(
-      "images/tangible_sync_style_right_banner_dark.svg",
-      IDR_SIGNIN_IMAGES_SHARED_RIGHT_BANNER_DARK_SVG);
+  html_source->AddResourcePath("images/right_banner_dark.svg",
+                               IDR_SIGNIN_IMAGES_SHARED_RIGHT_BANNER_DARK_SVG);
 }
 
 }  // namespace
diff --git a/chrome/browser/usb/chrome_usb_delegate.cc b/chrome/browser/usb/chrome_usb_delegate.cc
index 6e0e0f2..203635c 100644
--- a/chrome/browser/usb/chrome_usb_delegate.cc
+++ b/chrome/browser/usb/chrome_usb_delegate.cc
@@ -36,8 +36,8 @@
 using ::content::UsbChooser;
 
 UsbChooserContext* GetChooserContext(content::BrowserContext* browser_context) {
-  return UsbChooserContextFactory::GetForProfile(
-      Profile::FromBrowserContext(browser_context));
+  auto* profile = Profile::FromBrowserContext(browser_context);
+  return profile ? UsbChooserContextFactory::GetForProfile(profile) : nullptr;
 }
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -255,21 +255,28 @@
 bool ChromeUsbDelegate::CanRequestDevicePermission(
     content::BrowserContext* browser_context,
     const url::Origin& origin) {
-  return GetChooserContext(browser_context)->CanRequestObjectPermission(origin);
+  return browser_context &&
+         GetChooserContext(browser_context)->CanRequestObjectPermission(origin);
 }
 
 void ChromeUsbDelegate::RevokeDevicePermissionWebInitiated(
     content::BrowserContext* browser_context,
     const url::Origin& origin,
     const device::mojom::UsbDeviceInfo& device) {
-  GetChooserContext(browser_context)
-      ->RevokeDevicePermissionWebInitiated(origin, device);
+  auto* chooser_context = GetChooserContext(browser_context);
+  if (chooser_context) {
+    chooser_context->RevokeDevicePermissionWebInitiated(origin, device);
+  }
 }
 
 const device::mojom::UsbDeviceInfo* ChromeUsbDelegate::GetDeviceInfo(
     content::BrowserContext* browser_context,
     const std::string& guid) {
-  return GetChooserContext(browser_context)->GetDeviceInfo(guid);
+  auto* chooser_context = GetChooserContext(browser_context);
+  if (!chooser_context) {
+    return nullptr;
+  }
+  return chooser_context->GetDeviceInfo(guid);
 }
 
 bool ChromeUsbDelegate::HasDevicePermission(
@@ -279,14 +286,19 @@
   if (IsDevicePermissionAutoGranted(origin, device))
     return true;
 
-  return GetChooserContext(browser_context)
-      ->HasDevicePermission(origin, device);
+  return browser_context && GetChooserContext(browser_context)
+                                ->HasDevicePermission(origin, device);
 }
 
 void ChromeUsbDelegate::GetDevices(
     content::BrowserContext* browser_context,
     blink::mojom::WebUsbService::GetDevicesCallback callback) {
-  GetChooserContext(browser_context)->GetDevices(std::move(callback));
+  auto* chooser_context = GetChooserContext(browser_context);
+  if (!chooser_context) {
+    std::move(callback).Run(std::vector<device::mojom::UsbDeviceInfoPtr>());
+    return;
+  }
+  chooser_context->GetDevices(std::move(callback));
 }
 
 void ChromeUsbDelegate::GetDevice(
@@ -295,23 +307,33 @@
     base::span<const uint8_t> blocked_interface_classes,
     mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
     mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client) {
-  GetChooserContext(browser_context)
-      ->GetDevice(guid, blocked_interface_classes, std::move(device_receiver),
-                  std::move(device_client));
+  auto* chooser_context = GetChooserContext(browser_context);
+  if (chooser_context) {
+    chooser_context->GetDevice(guid, blocked_interface_classes,
+                               std::move(device_receiver),
+                               std::move(device_client));
+  }
 }
 
 void ChromeUsbDelegate::AddObserver(content::BrowserContext* browser_context,
                                     Observer* observer) {
+  if (!browser_context) {
+    return;
+  }
   GetContextObserver(browser_context)->AddObserver(observer);
 }
 
 void ChromeUsbDelegate::RemoveObserver(content::BrowserContext* browser_context,
                                        Observer* observer) {
+  if (!browser_context) {
+    return;
+  }
   GetContextObserver(browser_context)->RemoveObserver(observer);
 }
 
 ChromeUsbDelegate::ContextObservation* ChromeUsbDelegate::GetContextObserver(
     content::BrowserContext* browser_context) {
+  CHECK(browser_context);
   if (!base::Contains(observations_, browser_context)) {
     observations_.emplace(browser_context, std::make_unique<ContextObservation>(
                                                this, browser_context));
diff --git a/chrome/browser/usb/chrome_usb_delegate_unittest.cc b/chrome/browser/usb/chrome_usb_delegate_unittest.cc
index 9706c066..7db935ef 100644
--- a/chrome/browser/usb/chrome_usb_delegate_unittest.cc
+++ b/chrome/browser/usb/chrome_usb_delegate_unittest.cc
@@ -2,6 +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/usb/chrome_usb_delegate.h"
 #include "build/build_config.h"
 #include "extensions/buildflags/buildflags.h"
 
@@ -632,4 +633,23 @@
               device::mojom::UsbClaimInterfaceResult::kProtectedClass);
   }
 }
+
+TEST_F(ChromeUsbDelegateTest, BrowserContextIsNull) {
+  ChromeUsbDelegate chrome_usb_delegate;
+  url::Origin origin = url::Origin::Create(GURL(kDefaultTestUrl));
+  EXPECT_FALSE(chrome_usb_delegate.CanRequestDevicePermission(
+      /*browser_context=*/nullptr, origin));
+  EXPECT_FALSE(chrome_usb_delegate.HasDevicePermission(
+      /*browser_context=*/nullptr, origin, device::mojom::UsbDeviceInfo()));
+  EXPECT_EQ(nullptr, chrome_usb_delegate.GetDeviceInfo(
+                         /*browser_context=*/nullptr,
+                         base::Uuid::GenerateRandomV4().AsLowercaseString()));
+
+  TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> get_devices_future;
+  chrome_usb_delegate.GetDevices(/*browser_context=*/nullptr,
+                                 get_devices_future.GetCallback());
+  EXPECT_TRUE(get_devices_future.Get().empty());
+
+  // TODO(crbug.com/1303193): Test GetDevice with null browser_context.
+}
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 7951201..dac0f7ce 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -396,6 +396,7 @@
     "//components/site_engagement/content",
     "//components/site_engagement/core/mojom:mojo_bindings",
     "//components/sync",
+    "//components/ukm",
     "//components/user_manager",
     "//components/web_package",
     "//components/webapps/browser",
diff --git a/chrome/browser/web_applications/daily_metrics_helper.cc b/chrome/browser/web_applications/daily_metrics_helper.cc
index dc335f0..ebe2eea2 100644
--- a/chrome/browser/web_applications/daily_metrics_helper.cc
+++ b/chrome/browser/web_applications/daily_metrics_helper.cc
@@ -14,7 +14,6 @@
 #include "base/functional/bind.h"
 #include "base/numerics/clamped_math.h"
 #include "base/strings/string_piece_forward.h"
-#include "base/types/pass_key.h"
 #include "base/value_iterators.h"
 #include "base/values.h"
 #include "chrome/browser/metrics/ukm_background_recorder_service.h"
@@ -24,8 +23,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
-#include "components/sync/base/model_type.h"
-#include "components/sync/driver/sync_service_utils.h"
+#include "components/ukm/app_source_url_recorder.h"
 #include "content/public/browser/browser_thread.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -46,34 +44,16 @@
   return std::max(1, result);
 }
 
-// Checks whether the user enabled syncing apps, which is required to record
-// app-keyed metrics.
-// TODO(crbug.com/1441376): Deprecated. Switch to AppKm so UKM code can enforce
-// these checks.
-bool ShouldRecordAppKeyedMetrics(syncer::SyncService* sync_service) {
-  switch (
-      syncer::GetUploadToGoogleState(sync_service, syncer::ModelType::APPS)) {
-    case syncer::UploadState::NOT_ACTIVE:
-      return false;
-    case syncer::UploadState::INITIALIZING:
-      // Note that INITIALIZING is considered good enough, because syncing apps
-      // is known to be enabled, and transient errors don't really matter here.
-    case syncer::UploadState::ACTIVE:
-      return true;
-  }
-}
-
 }  // namespace
 
-// This class exists just to use a `PassKey` so `UkmRecorder` can control the
-// emission of Web app UKMs.
+// This class exists just to be friended by `AppSourceUrlRecorder` to control
+// the emission of web app AppKMs.
 class DesktopWebAppUkmRecorder {
  public:
   static void Emit(const DailyInteraction& record) {
     DCHECK(record.start_url.is_valid());
     ukm::SourceId source_id =
-        ukm::UkmRecorder::GetSourceIdForDesktopWebAppStartUrl(
-            base::PassKey<DesktopWebAppUkmRecorder>(), record.start_url);
+        ukm::AppSourceUrlRecorder::GetSourceIdForPWA(record.start_url);
     ukm::builders::WebApp_DailyInteraction builder(source_id);
     builder.SetUsed(true)
         .SetInstalled(record.installed)
@@ -102,6 +82,7 @@
     }
 #endif
     builder.Record(ukm::UkmRecorder::Get());
+    ukm::AppSourceUrlRecorder::MarkSourceForDeletion(source_id);
   }
 };
 
@@ -233,11 +214,7 @@
       origin, base::BindOnce(&EmitIfSourceIdExists, std::move(record)));
 }
 
-void EmitRecords(Profile* profile, syncer::SyncService* sync_service) {
-  if (!ShouldRecordAppKeyedMetrics(sync_service)) {
-    return;
-  }
-
+void EmitRecords(Profile* profile) {
   const base::Value::Dict& urls_to_features =
       profile->GetPrefs()->GetDict(prefs::kWebAppsDailyMetrics);
 
@@ -285,22 +262,19 @@
 DailyInteraction::DailyInteraction(const DailyInteraction&) = default;
 DailyInteraction::~DailyInteraction() = default;
 
-void FlushOldRecordsAndUpdate(DailyInteraction& record,
-                              Profile* profile,
-                              syncer::SyncService* sync_service) {
+void FlushOldRecordsAndUpdate(DailyInteraction& record, Profile* profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (metrics::date_changed_helper::HasDateChangedSinceLastCall(
           profile->GetPrefs(), prefs::kWebAppsDailyMetricsDate)) {
-    EmitRecords(profile, sync_service);
+    EmitRecords(profile);
     RemoveRecords(profile->GetPrefs());
   }
   UpdateRecord(record, profile->GetPrefs());
 }
 
-void FlushAllRecordsForTesting(Profile* profile,  // IN-TEST
-                               syncer::SyncService* sync_service) {
+void FlushAllRecordsForTesting(Profile* profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  EmitRecords(profile, sync_service);
+  EmitRecords(profile);
   RemoveRecords(profile->GetPrefs());
 }
 
diff --git a/chrome/browser/web_applications/daily_metrics_helper.h b/chrome/browser/web_applications/daily_metrics_helper.h
index f83788ed..530dd1c5 100644
--- a/chrome/browser/web_applications/daily_metrics_helper.h
+++ b/chrome/browser/web_applications/daily_metrics_helper.h
@@ -13,14 +13,12 @@
 class PrefRegistrySimple;
 class Profile;
 
-namespace syncer {
-class SyncService;
-}  // namespace syncer
-
 namespace web_app {
 
 struct DailyInteraction {
   // Required.
+  // TODO(crbug.com/1442799): Use manifest_identity_url here instead of
+  // start_url.
   GURL start_url;
   // Implied bool used = true;
   bool installed = false;
@@ -47,14 +45,11 @@
 // Emits UKM metrics for existing records if the date has changed, removing them
 // from storage. Then stores the given record, updating any stored values for
 // that start_url (ie. replacing or summing as appropriate).
-void FlushOldRecordsAndUpdate(DailyInteraction& record,
-                              Profile* profile,
-                              syncer::SyncService* sync_service);
+void FlushOldRecordsAndUpdate(DailyInteraction& record, Profile* profile);
 
 // Emits UKM metrics for all existing records. Note that this is asynchronous
 // unless |SkipOriginCheckForTesting| has been called.
-void FlushAllRecordsForTesting(Profile* profile,
-                               syncer::SyncService* sync_service);
+void FlushAllRecordsForTesting(Profile* profile);
 
 // Skip the origin check, which is async and requires a history service.
 void SkipOriginCheckForTesting();
diff --git a/chrome/browser/web_applications/daily_metrics_helper_unittest.cc b/chrome/browser/web_applications/daily_metrics_helper_unittest.cc
index d6bca5f..3036d2e 100644
--- a/chrome/browser/web_applications/daily_metrics_helper_unittest.cc
+++ b/chrome/browser/web_applications/daily_metrics_helper_unittest.cc
@@ -4,12 +4,19 @@
 
 #include "chrome/browser/web_applications/daily_metrics_helper.h"
 
+#include <stdint.h>
+#include <vector>
+
+#include "base/numerics/clamped_math.h"
+#include "base/strings/string_piece_forward.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "chrome/browser/web_applications/test/web_app_test.h"
-#include "components/sync/test/test_sync_service.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/ukm/test_ukm_recorder.h"
+#include "content/public/test/browser_task_environment.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/mojom/ukm_interface.mojom.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -49,7 +56,7 @@
   }
 
   void FlushOldRecordsAndUpdate(DailyInteraction record) {
-    web_app::FlushOldRecordsAndUpdate(record, profile(), &sync_service_);
+    web_app::FlushOldRecordsAndUpdate(record, profile());
   }
 
   void RecordSomethingTheNextDaySoItEmits() {
@@ -61,7 +68,6 @@
   DailyMetricsHelperTest(const DailyMetricsHelperTest&) = delete;
   DailyMetricsHelperTest& operator=(const DailyMetricsHelperTest&) = delete;
 
-  syncer::TestSyncService sync_service_;
   ukm::TestAutoSetUkmRecorder ukm_recorder_;
 };
 
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_ash_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_ash_browsertest.cc
index 07cc578..0379e8ed 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_ash_browsertest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_ash_browsertest.cc
@@ -17,12 +17,12 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/ash/login/existing_user_controller.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
-#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
diff --git a/chrome/browser/webapps/chrome_webapps_client.cc b/chrome/browser/webapps/chrome_webapps_client.cc
index daa2036..58ec9b4ce 100644
--- a/chrome/browser/webapps/chrome_webapps_client.cc
+++ b/chrome/browser/webapps/chrome_webapps_client.cc
@@ -124,14 +124,14 @@
                                         const AddToHomescreenParams& params) {
   WebApkInstallService::Get(web_contents->GetBrowserContext())
       ->InstallAsync(web_contents, *(params.shortcut_info), params.primary_icon,
-                     params.has_maskable_primary_icon, params.install_source);
+                     params.install_source);
 }
 
 void ChromeWebappsClient::InstallShortcut(content::WebContents* web_contents,
                                           const AddToHomescreenParams& params) {
   ShortcutHelper::AddToLauncherWithSkBitmap(
       web_contents, *(params.shortcut_info), params.primary_icon,
-      params.has_maskable_primary_icon, params.installable_status);
+      params.installable_status);
 }
 #endif
 
diff --git a/chrome/browser/webauthn/android/BUILD.gn b/chrome/browser/webauthn/android/BUILD.gn
index 18cb2484..81fd40a 100644
--- a/chrome/browser/webauthn/android/BUILD.gn
+++ b/chrome/browser/webauthn/android/BUILD.gn
@@ -20,6 +20,7 @@
     "//chrome/browser/notifications:java",
     "//components/browser_ui/notifications/android:java",
     "//content/public/android:content_main_dex_java",
+    "//services/device/public/java:device_feature_list_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_core_core_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
diff --git a/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java b/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java
index 537ed3c7..c48582a 100644
--- a/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java
+++ b/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java
@@ -21,7 +21,6 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.annotation.RequiresApi;
 import androidx.core.app.NotificationCompat;
 import androidx.core.app.NotificationManagerCompat;
 import androidx.fragment.app.Fragment;
@@ -39,6 +38,7 @@
 import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory;
 import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
 import org.chromium.chrome.modules.cablev2_authenticator.Cablev2AuthenticatorModule;
+import org.chromium.device.DeviceFeatureList;
 
 /**
  * Provides a UI that attempts to install the caBLEv2 Authenticator module. If already installed, or
@@ -232,17 +232,18 @@
 
         // GMSCore will immediately fail all requests if a screenlock
         // isn't configured.
-        return hasScreenLockConfigured();
-    }
+        final Context context = ContextUtils.getApplicationContext();
+        KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+        if (!km.isDeviceSecure()) {
+            return false;
+        }
 
-    // canDeviceSupportCable has checked that the system is >= N (API level 24)
-    // before calling this function.
-    @RequiresApi(24)
-    private static boolean hasScreenLockConfigured() {
-        KeyguardManager km =
-                (KeyguardManager) ContextUtils.getApplicationContext().getSystemService(
-                        Context.KEYGUARD_SERVICE);
-        return km.isDeviceSecure();
+        if (DeviceFeatureList.isEnabled(
+                    DeviceFeatureList.WEBAUTHN_HYBRID_LINK_WITHOUT_NOTIFICATIONS)) {
+            return true;
+        }
+
+        return NotificationManagerCompat.from(context).areNotificationsEnabled();
     }
 
     @NativeMethods
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 8725215..50a5c07 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -145,6 +145,7 @@
     case device::AuthenticatorType::kPhone:
       return password_manager::PasskeyCredential::Source::kAndroidPhone;
     case device::AuthenticatorType::kChromeOS:
+    case device::AuthenticatorType::kICloudKeychain:
     case device::AuthenticatorType::kOther:
       return password_manager::PasskeyCredential::Source::kOther;
   }
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index a11adf0..b02130e16 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1683791730-4160bba116dce95a15db0bb219de87ab4209124e.profdata
+chrome-chromeos-amd64-generic-main-1683878385-50b4f0da232e96547ce60bf37db4be1efa50f8c6.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 6b62aa4..ac15601 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1683805465-28c841c5a895304a001bb8d03a06fbb1814563db.profdata
+chrome-mac-arm-main-1683878385-ab222097a7abee7a42bb18101f8bbb462127d04d.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 64a6bc5..e120ab8 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1683795574-5ceffb90771f8c50dc467752e784ddc66c8ad773.profdata
+chrome-win32-main-1683871179-3a20678bed11b8a8bc43ba9b1656c5d13db01e70.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 40448c82..84d1740a 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1683795574-3bcb54447c509b5f9208d295f94db074e25b1e6a.profdata
+chrome-win64-main-1683871179-bc2e776324c3b54f3d159771afd0becb2465be90.profdata
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 8e13f7a..1ef05c9 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -187,6 +187,7 @@
     "//components/cdm/common",
     "//components/client_hints/common",
     "//components/cloud_devices/common",
+    "//components/commerce/core:commerce_constants",
     "//components/component_updater",
     "//components/content_settings/core/common",
     "//components/crash/core/common",
diff --git a/chrome/common/DEPS b/chrome/common/DEPS
index 54c90f3d..fed3e13f 100644
--- a/chrome/common/DEPS
+++ b/chrome/common/DEPS
@@ -11,6 +11,7 @@
   "+components/bookmarks/common",
   "+components/cdm/common",
   "+components/cloud_devices/common",
+  "+components/commerce/core",
   "+components/content_settings/core/common",
   "+components/crash/core/app",
   "+components/history_clusters",
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 39d191d5..2eab225 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -650,6 +650,9 @@
 // handling policy is specified in Preferences.
 const char kWebRtcIPHandlingPolicy[] = "webrtc-ip-handling-policy";
 
+// Specify the initial window user title: --window-name="My custom title"
+const char kWindowName[] = "window-name";
+
 // Specify the initial window position: --window-position=x,y
 const char kWindowPosition[] = "window-position";
 
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index c777bf7a..d0b9e49 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -193,6 +193,7 @@
 extern const char kWebRtcRemoteEventLogUploadDelayMs[];
 extern const char kWebRtcRemoteEventLogUploadNoSuppression[];
 extern const char kWebRtcIPHandlingPolicy[];
+extern const char kWindowName[];
 extern const char kWindowPosition[];
 extern const char kWindowSize[];
 extern const char kWindowWorkspace[];
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index b96d5d6..7dbefd3 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -447,6 +447,7 @@
     ShelfItemStatus status;
     boolean showsTooltip;
     boolean pinnedByPolicy;
+    boolean pinStateForcedByType;
     boolean hasNotification;
   };
 
diff --git a/chrome/common/extensions/api/input_method_private.json b/chrome/common/extensions/api/input_method_private.json
index 4368d60..3f2733e 100644
--- a/chrome/common/extensions/api/input_method_private.json
+++ b/chrome/common/extensions/api/input_method_private.json
@@ -77,7 +77,6 @@
           "spellCheck": {"type": "boolean", "description": "Whether the text field wants spell-check."},
           "shouldDoLearning": {"type": "boolean", "description": "Whether text entered into the text field should be used to improve typing suggestions for the user."},
           "focusReason": {"$ref": "FocusReason", "description": "How the text field was focused"},
-          "hasBeenPassword": {"type": "boolean", "description": "DEPRECATED: when this was true, we now just set input field type to be password. Was: Whether the text field has ever been a password field."},
           "appKey": {"type": "string", "optional": true, "description": "Key of the app associated with this text field if any."}
         }
       },
diff --git a/chrome/common/extensions/api/printing.idl b/chrome/common/extensions/api/printing.idl
index 1ebd8bb..528e7ed 100644
--- a/chrome/common/extensions/api/printing.idl
+++ b/chrome/common/extensions/api/printing.idl
@@ -110,6 +110,9 @@
     // The printer is unreachable and doesn't accept print jobs.
     UNREACHABLE,
 
+    // The SSL certificate is expired. Printer accepts jobs but they fail.
+    EXPIRED_CERTIFICATE,
+
     // The printer is available.
     AVAILABLE
   };
diff --git a/chrome/common/mac/app_mode_chrome_locator.mm b/chrome/common/mac/app_mode_chrome_locator.mm
index d5cb9ba..9fae56b 100644
--- a/chrome/common/mac/app_mode_chrome_locator.mm
+++ b/chrome/common/mac/app_mode_chrome_locator.mm
@@ -9,9 +9,9 @@
 
 #include <set>
 
+#include "base/apple/bridging.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/common/chrome_constants.h"
@@ -84,10 +84,10 @@
   // Retrieve the last-run Chrome bundle location.
   base::FilePath last_run_bundle_path;
   {
-    NSString* cr_bundle_path_ns = base::mac::CFToNSOwnershipCast(
+    NSString* cr_bundle_path_ns = base::apple::CFToNSOwnershipCast(
         base::mac::CFCastStrict<CFStringRef>(CFPreferencesCopyAppValue(
-            base::mac::NSToCFPtrCast(app_mode::kLastRunAppBundlePathPrefsKey),
-            base::mac::NSToCFPtrCast(bundle_id))));
+            base::apple::NSToCFPtrCast(app_mode::kLastRunAppBundlePathPrefsKey),
+            base::apple::NSToCFPtrCast(bundle_id))));
     last_run_bundle_path = base::mac::NSStringToFilePath(cr_bundle_path_ns);
   }
 
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index c1ded520..dd2e6c81 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -470,6 +470,9 @@
 const char kSmbSharesLearnMoreURL[] =
     "https://support.google.com/chromebook?p=network_file_shares";
 
+const char kSpeakOnMuteDetectionLearnMoreURL[] =
+    "https://support.google.com/chromebook?p=mic-mute";
+
 const char kSuggestedContentLearnMoreURL[] =
     "https://support.google.com/chromebook/?p=explorecontent";
 
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 222c94a6..e836baf 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -445,6 +445,10 @@
 // The URL for the "Learn more" page for the network file shares settings page.
 extern const char kSmbSharesLearnMoreURL[];
 
+// The URL for the "Learn more" page for Speak-on-mute Detection in the privacy
+// hub page.
+extern const char kSpeakOnMuteDetectionLearnMoreURL[];
+
 // The URL for the "Learn more" page for Suggested Content in the privacy page.
 extern const char kSuggestedContentLearnMoreURL[];
 
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index bb2a2a6..0acba84 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -8,6 +8,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "build/config/chromebox_for_meetings/buildflags.h"
+#include "components/commerce/core/commerce_constants.h"
 #include "components/history_clusters/history_clusters_internals/webui/url_constants.h"
 #include "components/lens/buildflags.h"
 #include "components/nacl/common/buildflags.h"
@@ -430,6 +431,7 @@
 const char kOsUINearbyInternalsURL[] = "os://nearby-internals";
 const char kOsUINetworkURL[] = "os://network";
 const char kOsUINetExportURL[] = "os://net-export";
+const char kOsUINotificationTesterURL[] = "os://notification-tester";
 const char kOsUIPrefsInternalsURL[] = "os://prefs-internals";
 const char kOsUIRestartURL[] = "os://restart";
 const char kOsUISettingsURL[] = "os://settings";
@@ -676,6 +678,7 @@
     kChromeUIBrowsingTopicsInternalsHost,
     kChromeUIChromeURLsHost,
     kChromeUIComponentsHost,
+    commerce::kChromeUICommerceInternalsHost,
     kChromeUICrashesHost,
     kChromeUICreditsHost,
 #if BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OFFICIAL_BUILD)
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 1a368e3..a06b689 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -403,6 +403,7 @@
 extern const char kOsUIMultiDeviceInternalsURL[];
 extern const char kOsUINearbyInternalsURL[];
 extern const char kOsUINetworkURL[];
+extern const char kOsUINotificationTesterURL[];
 extern const char kOsUIPrefsInternalsURL[];
 extern const char kOsUIRestartURL[];
 extern const char kOsUISettingsURL[];
diff --git a/chrome/installer/mac/sign_chrome.py b/chrome/installer/mac/sign_chrome.py
index ba29722..434102c 100755
--- a/chrome/installer/mac/sign_chrome.py
+++ b/chrome/installer/mac/sign_chrome.py
@@ -20,8 +20,7 @@
     specific certificate hashes.
 
     Args:
-        config_args: Dict of args to expand as kwargs to the config class's
-            constructor.
+        config_args: List of args to expand to the config class's constructor.
         development: Boolean indicating whether or not to modify the chosen
             config for development testing.
 
@@ -142,7 +141,6 @@
         choices=model.NotarizeAndStapleLevel.valid_strings(),
         const='staple',
         default='none',
-        type=model.NotarizeAndStapleLevel.from_string,
         help='Specifies the requested notarization actions to be taken. '
         '`none` causes no notarization tasks to be performed. '
         '`nowait` submits the signed application and packaging to Apple for '
@@ -160,6 +158,12 @@
 
     args = parser.parse_args()
 
+    notarization = model.NotarizeAndStapleLevel.from_string(args.notarize)
+    if notarization.should_notarize():
+        if not args.notary_user or not args.notary_password:
+            parser.error('The `--notary-user` and `--notary-password` '
+                         'arguments are required if notarizing.')
+
     config = create_config(
         model.pick(args, (
             'identity',
@@ -169,14 +173,8 @@
             'notary_asc_provider',
             'notary_team_id',
             'notarization_tool',
-            'notarize',
         )), args.development)
 
-    if config.notarize.should_notarize():
-        if not args.notary_user or not args.notary_password:
-            parser.error('The `--notary-user` and `--notary-password` '
-                         'arguments are required if notarizing.')
-
     if config.notarization_tool == model.NotarizationTool.NOTARYTOOL:
         # Let the config override notary_team_id, including a potentially
         # unspecified argument.
@@ -195,6 +193,7 @@
         paths,
         config,
         disable_packaging=args.disable_packaging,
+        notarization=notarization,
         skip_brands=args.skip_brands,
         channels=args.channels)
 
diff --git a/chrome/installer/mac/signing/config.py b/chrome/installer/mac/signing/config.py
index e232884..3e8acfec 100644
--- a/chrome/installer/mac/signing/config.py
+++ b/chrome/installer/mac/signing/config.py
@@ -4,7 +4,7 @@
 
 import os.path
 
-from .model import Distribution, NotarizeAndStapleLevel, NotarizationTool
+from .model import Distribution, NotarizationTool
 
 
 class ConfigError(Exception):
@@ -37,8 +37,7 @@
                  notary_asc_provider=None,
                  notary_team_id=None,
                  codesign_requirements_basic='',
-                 notarization_tool=None,
-                 notarize=NotarizeAndStapleLevel.STAPLE):
+                 notarization_tool=None):
         """Creates a CodeSignConfig that will sign the product using the static
         properties on the class, using the code signing identity passed to the
         constructor.
@@ -69,7 +68,6 @@
                 otherwise.
             notarization_tool: The tool to use to communicate with the Apple
                 notary service. If None, the config will choose a default.
-            notarize: The |model.NotarizeAndStapleLevel|.
         """
         assert identity is not None
         assert type(identity) is str
@@ -81,7 +79,6 @@
         self._codesign_requirements_basic = codesign_requirements_basic
         self._notary_team_id = notary_team_id
         self._notarization_tool = notarization_tool
-        self._notarize = notarize
 
     @staticmethod
     def is_chrome_branded():
@@ -157,13 +154,6 @@
         return None
 
     @property
-    def notarize(self):
-        """Returns the |model.NotarizeAndStapleLevel| that controls how, if
-        at all, notarization and stapling of CodeSignedProducts should occur.
-        """
-        return self._notarize
-
-    @property
     def app_product(self):
         """Returns the product name that is used for the outer .app bundle.
         This is displayed to the user in Finder.
diff --git a/chrome/installer/mac/signing/model.py b/chrome/installer/mac/signing/model.py
index 72991f6..760ff42 100644
--- a/chrome/installer/mac/signing/model.py
+++ b/chrome/installer/mac/signing/model.py
@@ -222,10 +222,7 @@
 
     @classmethod
     def from_string(cls, str):
-        try:
-            return cls[str.upper().replace('-', '_')]
-        except KeyError:
-            raise ValueError(f'Invalid NotarizeAndStapleLevel: {str}')
+        return cls[str.upper().replace('-', '_')]
 
 
 class NotarizationTool(enum.Enum):
diff --git a/chrome/installer/mac/signing/pipeline.py b/chrome/installer/mac/signing/pipeline.py
index 2b5c558..89b2139 100644
--- a/chrome/installer/mac/signing/pipeline.py
+++ b/chrome/installer/mac/signing/pipeline.py
@@ -645,6 +645,7 @@
 def sign_all(orig_paths,
              config,
              disable_packaging=False,
+             notarization=model.NotarizeAndStapleLevel.STAPLE,
              skip_brands=[],
              channels=[]):
     """For each distribution in |config|, performs customization, signing, and
@@ -658,6 +659,9 @@
             unpackaged signed app bundle will be copied to |paths.output|. If
             False, the packaging specified in the distribution will be
             performed.
+        notarization: The level of notarization to be performed. If
+            |disable_packaging| is False, the packages (dmg/pkg) will undergo
+            the same notarization.
         skip_brands: A list of brand code strings. If a distribution has a brand
             code in this list, or if a distribution has a brand code and
             |skip_brands| contains *, that distribution will be skipped.
@@ -683,7 +687,7 @@
 
                 # If not packaging and not notarizing, then simply drop the
                 # signed bundle in the output directory when done signing.
-                if not do_packaging and not config.notarize.should_notarize():
+                if not do_packaging and not notarization.should_notarize():
                     dest_dir = paths.output
                 else:
                     dest_dir = notary_paths.work
@@ -704,7 +708,7 @@
 
                 # If the build products are to be notarized, ZIP the app bundle
                 # and submit it for notarization.
-                if config.notarize.should_notarize():
+                if notarization.should_notarize():
                     zip_file = os.path.join(
                         notary_paths.work,
                         dist_config.packaging_basename + '.zip')
@@ -718,10 +722,10 @@
 
         # If needed, wait for app notarization results to come back, and staple
         # if required.
-        if config.notarize.should_wait():
+        if notarization.should_wait():
             for result in notarize.wait_for_results(uuids_to_config.keys(),
                                                     config):
-                if config.notarize.should_staple():
+                if notarization.should_staple():
                     dist_config = uuids_to_config[result]
                     dest_dir = os.path.join(
                         notary_paths.work,
@@ -750,23 +754,23 @@
                 if dist.package_as_dmg:
                     dmg_path = _package_and_sign_dmg(paths, dist_config)
 
-                    if config.notarize.should_notarize():
+                    if notarization.should_notarize():
                         uuid = notarize.submit(dmg_path, dist_config)
                         uuids_to_package_path[uuid] = dmg_path
 
                 if dist.package_as_pkg:
                     pkg_path = _package_and_sign_pkg(paths, dist_config)
 
-                    if config.notarize.should_notarize():
+                    if notarization.should_notarize():
                         uuid = notarize.submit(pkg_path, dist_config)
                         uuids_to_package_path[uuid] = pkg_path
 
             # If needed, wait for package notarization results to come back, and
             # staple if required.
-            if config.notarize.should_wait():
+            if notarization.should_wait():
                 for result in notarize.wait_for_results(
                         uuids_to_package_path.keys(), config):
-                    if config.notarize.should_staple():
+                    if notarization.should_staple():
                         package_path = uuids_to_package_path[result]
                         notarize.staple(package_path)
 
diff --git a/chrome/installer/mac/signing/pipeline_test.py b/chrome/installer/mac/signing/pipeline_test.py
index 1122e63..5065f1d 100644
--- a/chrome/installer/mac/signing/pipeline_test.py
+++ b/chrome/installer/mac/signing/pipeline_test.py
@@ -1319,9 +1319,11 @@
         kwargs[
             '_package_and_sign_dmg'].return_value = '/$O/AppProduct-99.0.9999.99.dmg'
 
-        config = test_config.TestConfig(
-            notarize=model.NotarizeAndStapleLevel.NOWAIT)
-        pipeline.sign_all(self.paths, config)
+        config = test_config.TestConfig()
+        pipeline.sign_all(
+            self.paths,
+            config,
+            notarization=model.NotarizeAndStapleLevel.NOWAIT)
 
         self.assertEqual(1, kwargs['_package_installer_tools'].call_count)
 
@@ -1364,9 +1366,11 @@
         kwargs[
             '_package_and_sign_dmg'].return_value = '/$O/AppProduct-99.0.9999.99.dmg'
 
-        config = test_config.TestConfig(
-            notarize=model.NotarizeAndStapleLevel.WAIT_NOSTAPLE)
-        pipeline.sign_all(self.paths, config)
+        config = test_config.TestConfig()
+        pipeline.sign_all(
+            self.paths,
+            config,
+            notarization=model.NotarizeAndStapleLevel.WAIT_NOSTAPLE)
 
         self.assertEqual(1, kwargs['_package_installer_tools'].call_count)
 
@@ -1402,9 +1406,9 @@
         for attr in kwargs:
             manager.attach_mock(kwargs[attr], attr)
 
-        config = test_config.TestConfig(
-            notarize=model.NotarizeAndStapleLevel.NONE)
-        pipeline.sign_all(self.paths, config)
+        config = test_config.TestConfig()
+        pipeline.sign_all(
+            self.paths, config, notarization=model.NotarizeAndStapleLevel.NONE)
 
         self.assertEqual(1, kwargs['_package_installer_tools'].call_count)
 
@@ -1427,9 +1431,12 @@
         for attr in kwargs:
             manager.attach_mock(kwargs[attr], attr)
 
-        config = test_config.TestConfig(
-            notarize=model.NotarizeAndStapleLevel.NONE)
-        pipeline.sign_all(self.paths, config, disable_packaging=True)
+        config = test_config.TestConfig()
+        pipeline.sign_all(
+            self.paths,
+            config,
+            disable_packaging=True,
+            notarization=model.NotarizeAndStapleLevel.NONE)
 
         manager.assert_has_calls([
             # First customize the distribution and sign it.
@@ -1473,8 +1480,9 @@
                         package_as_pkg=True),
                 ]
 
-        config = Config(notarize=model.NotarizeAndStapleLevel.NONE)
-        pipeline.sign_all(self.paths, config)
+        config = Config()
+        pipeline.sign_all(
+            self.paths, config, notarization=model.NotarizeAndStapleLevel.NONE)
 
         self.assertEqual(1, kwargs['_package_installer_tools'].call_count)
         self.assertEqual(3, kwargs['_customize_and_sign_chrome'].call_count)
@@ -1526,10 +1534,11 @@
                     model.Distribution(),
                 ]
 
-        config = Config(notarize=model.NotarizeAndStapleLevel.NONE)
+        config = Config()
         pipeline.sign_all(
             self.paths,
             config,
+            notarization=model.NotarizeAndStapleLevel.NONE,
             skip_brands=skip_brands,
             channels=include_channels)
 
diff --git a/chrome/installer/mac/signing/signing.py b/chrome/installer/mac/signing/signing.py
index 03a1c34..c45ab6c 100644
--- a/chrome/installer/mac/signing/signing.py
+++ b/chrome/installer/mac/signing/signing.py
@@ -73,9 +73,9 @@
     path = os.path.join(paths.work, part.path)
     if _linker_signed_arm64_needs_force(path):
         command.append('--force')
-    if config.notarize.should_notarize():
-        # If the products will be notarized, the signature requires a secure
-        # timestamp.
+    if config.notary_user:
+        # Assume if the config has notary authentication information that the
+        # products will be notarized, which requires a secure timestamp.
         command.append('--timestamp')
     if part.sign_with_identifier:
         command.extend(['--identifier', part.identifier])
diff --git a/chrome/installer/mac/signing/signing_test.py b/chrome/installer/mac/signing/signing_test.py
index 0cbd942..4253c59 100644
--- a/chrome/installer/mac/signing/signing_test.py
+++ b/chrome/installer/mac/signing/signing_test.py
@@ -112,6 +112,7 @@
             '/$W/Test.app'
         ])
 
+
     def test_sign_part_needs_force(self, run_command,
                                    linker_signed_arm64_needs_force):
         linker_signed_arm64_needs_force.return_value = True
@@ -137,8 +138,7 @@
 
     def test_sign_part_no_notary(self, run_command,
                                  linker_signed_arm64_needs_force):
-        config = test_config.TestConfig(
-            notarize=model.NotarizeAndStapleLevel.NONE)
+        config = test_config.TestConfig(notary_user=None, notary_password=None)
         part = model.CodeSignedProduct('Test.app', 'test.signing.app')
         signing.sign_part(self.paths, config, part)
         run_command.assert_called_once_with(
diff --git a/chrome/renderer/autofill/fake_mojo_password_manager_driver.h b/chrome/renderer/autofill/fake_mojo_password_manager_driver.h
index c3743d8..fa16313 100644
--- a/chrome/renderer/autofill/fake_mojo_password_manager_driver.h
+++ b/chrome/renderer/autofill/fake_mojo_password_manager_driver.h
@@ -38,7 +38,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
   MOCK_METHOD(void,
-              ShowTouchToFill,
+              ShowKeyboardReplacingSurface,
               (autofill::mojom::SubmissionReadinessState),
               (override));
 #endif
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index f74ae2d..de0142c 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -584,14 +584,15 @@
     }
   }
 
-  // Helper to simulate that TouchTofFill was closed in order to test regular
-  // popups, e.g. |ShowPasswordSuggestions|.
-  void SimulateClosingTouchToFillIfAndroid(const std::string& element_id) {
+  // Helper to simulate that KeyboardReplacingSurface was closed in order to
+  // test regular popups, e.g. |ShowPasswordSuggestions|.
+  void SimulateClosingKeyboardReplacingSurfaceIfAndroid(
+      const std::string& element_id) {
     // Put the build guard here to save space in the caller test.
 #if BUILDFLAG(IS_ANDROID)
     FocusElement(element_id);
     // Don't show a keyboard, but let the caller to trigger it if needed.
-    password_autofill_agent_->TouchToFillClosed(
+    password_autofill_agent_->KeyboardReplacingSurfaceClosed(
         /*show_virtual_keyboard*/ false);
 #endif  // BUILDFLAG(IS_ANDROID)
   }
@@ -1125,7 +1126,7 @@
   SimulateOnFillPasswordForm(fill_data_);
 
   // Ensure TTF isn't in the foreground while this test simulates a typing user.
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   // No auto-fill should have taken place.
   CheckTextFieldsSuggestedState(
@@ -1777,93 +1778,95 @@
 }
 
 #if BUILDFLAG(IS_ANDROID)
-// Tests that TryToShowTouchToFill() works correctly for fillable and
-// non-fillable fields.
-TEST_F(PasswordAutofillAgentTest, TryToShowTouchToFillUsername) {
+// Tests that TryToShowKeyboardReplacingSurface() works correctly for fillable
+// and non-fillable fields.
+TEST_F(PasswordAutofillAgentTest, TryToShowKeyboardReplacingSurfaceUsername) {
   // Initially no fill data is available.
   WebInputElement random_element = GetInputElementByID("random_field");
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(username_element_));
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
-  EXPECT_FALSE(password_autofill_agent_->TryToShowTouchToFill(random_element));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      username_element_));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      random_element));
   EXPECT_FALSE(password_autofill_agent_->ShouldSuppressKeyboard());
 
   // This changes once fill data is simulated. |random_element| continue  to
   // have no fill data, though.
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(username_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      username_element_));
   EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   EXPECT_EQ(WebAutofillState::kPreviewed, username_element_.GetAutofillState());
   EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
 
-  EXPECT_CALL(
-      fake_driver_,
-      ShowTouchToFill(autofill::mojom::SubmissionReadinessState::kEmptyFields));
+  EXPECT_CALL(fake_driver_,
+              ShowKeyboardReplacingSurface(
+                  autofill::mojom::SubmissionReadinessState::kEmptyFields));
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(PasswordAutofillAgentTest, TryToShowTouchToFillPassword) {
+TEST_F(PasswordAutofillAgentTest, TryToShowKeyboardReplacingSurfacePassword) {
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
   EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
 
-  EXPECT_CALL(
-      fake_driver_,
-      ShowTouchToFill(autofill::mojom::SubmissionReadinessState::kEmptyFields));
+  EXPECT_CALL(fake_driver_,
+              ShowKeyboardReplacingSurface(
+                  autofill::mojom::SubmissionReadinessState::kEmptyFields));
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(PasswordAutofillAgentTest, TryToShowTouchToFillButDontEnableSubmission) {
+TEST_F(PasswordAutofillAgentTest,
+       TryToShowKeyboardReplacingSurfaceButDontEnableSubmission) {
   LoadHTML(kPasswordChangeFormHTML);
   UpdateUrlForHTML(kPasswordChangeFormHTML);
   UpdateUsernameAndPasswordElements();
   // Enable filling for the old password field.
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
   EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
 
   // As there are other input fields, don't enable automatic submission.
   EXPECT_CALL(
       fake_driver_,
-      ShowTouchToFill(
+      ShowKeyboardReplacingSurface(
           autofill::mojom::SubmissionReadinessState::kFieldAfterPasswordField));
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(PasswordAutofillAgentTest, TouchToFillSuppressesPopups) {
+TEST_F(PasswordAutofillAgentTest, KeyboardReplacingSurfaceSuppressesPopups) {
   SimulateOnFillPasswordForm(fill_data_);
   SimulateSuggestionChoice(username_element_);
-  EXPECT_CALL(fake_driver_, ShowTouchToFill);
+  EXPECT_CALL(fake_driver_, ShowKeyboardReplacingSurface);
   EXPECT_CALL(fake_driver_, ShowPasswordSuggestions).Times(0);
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(PasswordAutofillAgentTest, TouchToFillClosed) {
+TEST_F(PasswordAutofillAgentTest, KeyboardReplacingSurfaceClosed) {
   SimulateOnFillPasswordForm(fill_data_);
 
   auto previous_state = password_element_.GetAutofillState();
-  // Touch to fill will be shown multiple times until TouchToFillClosed()
-  // gets called.
+  // Touch to fill will be shown multiple times until
+  // KeyboardReplacingSurfaceClosed() gets called.
   FocusElement(kPasswordName);
   EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
 
-  EXPECT_CALL(fake_driver_, ShowTouchToFill);
+  EXPECT_CALL(fake_driver_, ShowKeyboardReplacingSurface);
   base::RunLoop().RunUntilIdle();
 
   // Make sure that resetting Touch To Fill resets the Autofill state.
-  password_autofill_agent_->TouchToFillClosed(true);
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  password_autofill_agent_->KeyboardReplacingSurfaceClosed(true);
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
   EXPECT_FALSE(password_autofill_agent_->ShouldSuppressKeyboard());
   EXPECT_EQ(previous_state, password_element_.GetAutofillState());
 
@@ -1878,7 +1881,7 @@
   EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   EXPECT_EQ(WebAutofillState::kPreviewed, password_element_.GetAutofillState());
 
-  EXPECT_CALL(fake_driver_, ShowTouchToFill);
+  EXPECT_CALL(fake_driver_, ShowKeyboardReplacingSurface);
   base::RunLoop().RunUntilIdle();
 }
 
@@ -1888,11 +1891,11 @@
   UpdateOnlyPasswordElement();
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 
   EXPECT_CALL(fake_driver_,
-              ShowTouchToFill(
+              ShowKeyboardReplacingSurface(
                   autofill::mojom::SubmissionReadinessState::kNoUsernameField));
   base::RunLoop().RunUntilIdle();
 }
@@ -1909,12 +1912,12 @@
 
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 
-  EXPECT_CALL(fake_driver_,
-              ShowTouchToFill(autofill::mojom::SubmissionReadinessState::
-                                  kFieldBetweenUsernameAndPassword));
+  EXPECT_CALL(fake_driver_, ShowKeyboardReplacingSurface(
+                                autofill::mojom::SubmissionReadinessState::
+                                    kFieldBetweenUsernameAndPassword));
   base::RunLoop().RunUntilIdle();
 }
 
@@ -1924,12 +1927,12 @@
   UpdateUsernameAndPasswordElements();
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 
   EXPECT_CALL(
       fake_driver_,
-      ShowTouchToFill(
+      ShowKeyboardReplacingSurface(
           autofill::mojom::SubmissionReadinessState::kFieldAfterPasswordField));
   base::RunLoop().RunUntilIdle();
 }
@@ -1937,12 +1940,12 @@
 TEST_F(PasswordAutofillAgentTest, SubmissionReadiness_EmptyFields) {
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 
-  EXPECT_CALL(
-      fake_driver_,
-      ShowTouchToFill(autofill::mojom::SubmissionReadinessState::kEmptyFields));
+  EXPECT_CALL(fake_driver_,
+              ShowKeyboardReplacingSurface(
+                  autofill::mojom::SubmissionReadinessState::kEmptyFields));
   base::RunLoop().RunUntilIdle();
 }
 
@@ -1952,12 +1955,12 @@
   ASSERT_FALSE(surname_element.IsNull());
   SimulateUserInputChangeForElement(&surname_element, "Smith");
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 
   EXPECT_CALL(
       fake_driver_,
-      ShowTouchToFill(
+      ShowKeyboardReplacingSurface(
           autofill::mojom::SubmissionReadinessState::kMoreThanTwoFields));
   base::RunLoop().RunUntilIdle();
 }
@@ -1969,12 +1972,12 @@
 
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 
-  EXPECT_CALL(
-      fake_driver_,
-      ShowTouchToFill(autofill::mojom::SubmissionReadinessState::kTwoFields));
+  EXPECT_CALL(fake_driver_,
+              ShowKeyboardReplacingSurface(
+                  autofill::mojom::SubmissionReadinessState::kTwoFields));
   base::RunLoop().RunUntilIdle();
 }
 
@@ -1984,24 +1987,25 @@
   UpdateOnlyUsernameElement();
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(username_element_));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      username_element_));
 
   EXPECT_CALL(fake_driver_,
-              ShowTouchToFill(
+              ShowKeyboardReplacingSurface(
                   autofill::mojom::SubmissionReadinessState::kNoPasswordField));
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(PasswordAutofillAgentTest, DontTryToShowTouchToFillOnReadonlyForm) {
+TEST_F(PasswordAutofillAgentTest,
+       DontTryToShowKeyboardReplacingSurfaceOnReadonlyForm) {
   SetElementReadOnly(username_element_, true);
   SetElementReadOnly(password_element_, true);
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(username_element_));
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      username_element_));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 }
 
 class PasswordAutofillAgentTestReadonlyElementVariationTest
@@ -2009,7 +2013,7 @@
       public testing::WithParamInterface<bool> {};
 
 TEST_P(PasswordAutofillAgentTestReadonlyElementVariationTest,
-       DontTryToShowTouchToFillOnReadonlyElement) {
+       DontTryToShowKeyboardReplacingSurfaceOnReadonlyElement) {
   bool is_password_readonly = GetParam();
 
   WebInputElement readonly_element =
@@ -2023,11 +2027,12 @@
   SimulateOnFillPasswordForm(fill_data_);
 
   // Firstly, check that Touch-To-Fill is not shown on the readonly element.
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(readonly_element));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      readonly_element));
 
   // Secondly, check that Touch-To-Fill can be shown on the editable element.
-  EXPECT_TRUE(password_autofill_agent_->TryToShowTouchToFill(editable_element));
+  EXPECT_TRUE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      editable_element));
 }
 
 INSTANTIATE_TEST_SUITE_P(ReadonlyElementVariation,
@@ -2038,7 +2043,8 @@
 // eligible for filling via manual fall back. In this case, the username_field
 // and password_field are not set. This test verifies that no failures are
 // recorded in PasswordManager.FirstRendererFillingResult.
-TEST_F(PasswordAutofillAgentTest, DontTryToShowTouchToFillSignUpForm) {
+TEST_F(PasswordAutofillAgentTest,
+       DontTryToShowKeyboardReplacingSurfaceSignUpForm) {
   LoadHTML(kSignupFormHTML);
 
   WebDocument document = GetMainFrame()->GetDocument();
@@ -2057,8 +2063,8 @@
 
   SimulateOnFillPasswordForm(fill_data_);
 
-  EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_FALSE(password_autofill_agent_->TryToShowKeyboardReplacingSurface(
+      password_element_));
 }
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -2183,7 +2189,7 @@
 // credential suggestion popup, and the user can autocomplete the password by
 // selecting the credential from the popup.
 TEST_F(PasswordAutofillAgentTest, ClickAndSelect) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   // SimulateElementClick() is called so that a user gesture is actually made
   // and the password can be filled. However, SimulateElementClick() does not
@@ -2257,7 +2263,7 @@
 // load and the element is autofilled, when the user clicks on an element that
 // has a matching username.
 TEST_F(PasswordAutofillAgentTest, CredentialsOnClick) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   // Simulate the browser sending back the login info.
   SimulateOnFillPasswordForm(fill_data_);
@@ -2285,7 +2291,7 @@
 // Tests that there is an autosuggestion from the password manager when the
 // user clicks on the password field.
 TEST_F(PasswordAutofillAgentTest, NoCredentialsOnPasswordClick) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   // Simulate the browser sending back the login info.
   SimulateOnFillPasswordForm(fill_data_);
@@ -2417,7 +2423,7 @@
 // test checks that the overwritten password is not reverted back.
 TEST_F(PasswordAutofillAgentTest,
        NoopEditingDoesNotOverwriteManuallyEditedPassword) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   fill_data_.wait_for_username = true;
   SimulateUsernameTyping(kAliceUsername);
@@ -2446,7 +2452,7 @@
 // that autofilling does not rewrite the username, if the value is already
 // there.
 TEST_F(PasswordAutofillAgentTest, AcceptingSuggestionDoesntRewriteUsername) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   fill_data_.wait_for_username = true;
   SimulateUsernameTyping(kAliceUsername);
@@ -2714,7 +2720,7 @@
   fill_data_.preferred_login.username_value.clear();
   fill_data_.additional_logins.clear();
 
-  SimulateClosingTouchToFillIfAndroid(kPasswordName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kPasswordName);
 
   password_element_.SetValue("");
   password_element_.SetAutofillState(WebAutofillState::kNotFilled);
@@ -2741,7 +2747,7 @@
   fill_data_.preferred_login.username_value.clear();
   fill_data_.additional_logins.clear();
 
-  SimulateClosingTouchToFillIfAndroid(kPasswordName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kPasswordName);
 
   password_element_.SetValue("");
   password_element_.SetAutofillState(WebAutofillState::kNotFilled);
@@ -2985,7 +2991,7 @@
   UpdateUrlForHTML(kPasswordChangeFormHTML);
   UpdateUsernameAndPasswordElements();
 
-  SimulateClosingTouchToFillIfAndroid(kPasswordName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kPasswordName);
 
   ClearUsernameAndPasswordFieldValues();
   fill_data_.wait_for_username = true;
@@ -3004,7 +3010,7 @@
   UpdateUrlForHTML(kPasswordChangeFormHTML);
   UpdateUsernameAndPasswordElements();
 
-  SimulateClosingTouchToFillIfAndroid(kPasswordName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kPasswordName);
 
   ClearUsernameAndPasswordFieldValues();
   fill_data_.wait_for_username = true;
@@ -3587,7 +3593,7 @@
 // Tests that a suggestion dropdown is shown on a password field even if a
 // username field is present.
 TEST_F(PasswordAutofillAgentTest, SuggestPasswordFieldSignInForm) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   // Simulate the browser sending back the login info.
   SimulateOnFillPasswordForm(fill_data_);
@@ -3896,7 +3902,7 @@
 }
 
 TEST_F(PasswordAutofillAgentTest, SuggestLatestCredentials) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   password_autofill_agent_->SetPasswordFillData(fill_data_);
   SimulateElementClick(kPasswordName);
@@ -4159,7 +4165,7 @@
 // Fill on account select for credentials with empty usernames:
 // Do not refill usernames if non-empty username is already selected.
 TEST_F(PasswordAutofillAgentTest, NoUsernameCredential) {
-  SimulateClosingTouchToFillIfAndroid(kUsernameName);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
 
   const char kPasswordForEmptyUsernameCredential[] = "empty";
   const char16_t kPasswordForEmptyUsernameCredential16[] = u"empty";
@@ -4251,8 +4257,8 @@
 
   // Simulate the browser sending the login info, but set |wait_for_username|
   // to prevent the form from being immediately filled because the test
-  // simulates filling with |FillSuggestion|, the function that TouchToFill
-  // uses.
+  // simulates filling with |FillSuggestion|, the function that
+  // KeyboardReplacingSurface uses.
   fill_data_.wait_for_username = true;
   SimulateOnFillPasswordForm(fill_data_);
 
@@ -4282,8 +4288,8 @@
 
   // Simulate the browser sending the login info, but set |wait_for_username|
   // to prevent the form from being immediately filled because the test
-  // simulates filling with |FillSuggestion|, the function that TouchToFill
-  // uses.
+  // simulates filling with |FillSuggestion|, the function that
+  // KeyboardReplacingSurface uses.
   fill_data_.wait_for_username = true;
   SimulateOnFillPasswordForm(fill_data_);
 
diff --git a/chrome/renderer/chrome_content_renderer_client_browsertest.cc b/chrome/renderer/chrome_content_renderer_client_browsertest.cc
index a0e95ec..d0ee7c4b 100644
--- a/chrome/renderer/chrome_content_renderer_client_browsertest.cc
+++ b/chrome/renderer/chrome_content_renderer_client_browsertest.cc
@@ -175,9 +175,8 @@
       browser()->tab_strip_model()->GetActiveWebContents();
 
   GURL video_url = https_server()->GetURL(GetParam().host, GetParam().path);
-  EXPECT_TRUE(ExecuteScript(web_contents, "appendEmbedToDOM('" +
-                                              video_url.spec() + "','" +
-                                              GetParam().type + "');"));
+  EXPECT_TRUE(ExecJs(web_contents, "appendEmbedToDOM('" + video_url.spec() +
+                                       "','" + GetParam().type + "');"));
   WaitForYouTubeRequest();
 }
 
@@ -189,9 +188,8 @@
      browser()->tab_strip_model()->GetActiveWebContents();
 
   GURL video_url = https_server()->GetURL(GetParam().host, GetParam().path);
-  EXPECT_TRUE(ExecuteScript(web_contents, "appendDataEmbedToDOM('" +
-                                              video_url.spec() + "','" +
-                                              GetParam().type + "');"));
+  EXPECT_TRUE(ExecJs(web_contents, "appendDataEmbedToDOM('" + video_url.spec() +
+                                       "','" + GetParam().type + "');"));
   WaitForYouTubeRequest();
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5b99dc1..7b089e5 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1378,6 +1378,7 @@
       "//chrome/browser/sharing/proto",
       "//chrome/browser/storage_access_api:permissions",
       "//chrome/browser/ui/color:color_headers",
+      "//chrome/browser/ui/side_panel:side_panel_enums",
       "//chrome/browser/ui/tabs:tab_enums",
       "//chrome/browser/ui/web_applications:browser_tests",
       "//chrome/browser/web_applications:browser_tests",
@@ -4438,7 +4439,6 @@
         "//chrome/browser/enterprise/connectors/device_trust/common",
         "//chrome/browser/image_decoder",
         "//chrome/browser/media/router:test_support",
-        "//chrome/browser/metrics/structured",
         "//chrome/browser/metrics/structured:test_support",
         "//chrome/browser/nearby_sharing/common",
         "//chrome/browser/policy:onc",
@@ -4530,7 +4530,7 @@
         "//chromeos/ui/frame",
         "//chromeos/ui/frame:test_support",
         "//chromeos/ui/wm",
-        "//components/access_code_cast/common",
+        "//components/access_code_cast/common:metrics",
         "//components/app_constants",
         "//components/app_restore",
         "//components/arc:arc_test_support",
@@ -5109,7 +5109,6 @@
       "//chrome/browser/chromeos:test_support",
       "//chrome/browser/chromeos/extensions/vpn_provider",
       "//chrome/browser/chromeos/extensions/wm",
-      "//chrome/browser/metrics/structured",
       "//chrome/browser/ui/web_applications:app_service_browser_tests",
       "//chrome/browser/web_applications:app_service_browser_tests",
       "//chrome/browser/web_applications:web_applications_test_support",
@@ -6653,9 +6652,12 @@
       "../browser/signin/bound_session_credentials/bound_session_cookie_controller_impl_unittest.cc",
       "../browser/signin/bound_session_credentials/bound_session_cookie_observer_unittest.cc",
       "../browser/signin/bound_session_credentials/bound_session_cookie_refresh_service_impl_unittest.cc",
+      "../browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc",
       "../browser/signin/bound_session_credentials/bound_session_request_throttled_listener_browser_impl_unittest.cc",
       "../browser/signin/bound_session_credentials/bound_session_test_cookie_manager.cc",
       "../browser/signin/bound_session_credentials/bound_session_test_cookie_manager.h",
+      "../browser/signin/bound_session_credentials/fake_bound_session_refresh_cookie_fetcher.cc",
+      "../browser/signin/bound_session_credentials/fake_bound_session_refresh_cookie_fetcher.h",
       "../browser/signin/bound_session_credentials/fake_bound_session_refresh_cookie_fetcher_unittest.cc",
       "../browser/signin/bound_session_credentials/registration_token_helper_unittest.cc",
       "../renderer/bound_session_credentials/bound_session_request_throttled_in_renderer_manager_unittest.cc",
@@ -7410,7 +7412,10 @@
       "../browser/search/ntp_features_unittest.cc",
     ]
     if (build_with_tflite_lib) {
-      sources += [ "../browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler_unittest.cc" ]
+      sources += [
+        "../browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_model_handler_unittest.cc",
+        "../browser/new_tab_page/modules/history_clusters/ranking/history_clusters_module_ranking_signals_unittest.cc",
+      ]
       data += [ "//components/test/data/omnibox/adder.tflite" ]
       deps += [ "//chrome/browser/new_tab_page/modules/history_clusters/ranking:history_clusters_module_proto" ]
     }
@@ -7710,6 +7715,7 @@
       "../browser/ui/ash/desks/chrome_saved_desk_delegate_unittest.cc",
       "../browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_unittest.cc",
       "../browser/ui/ash/device_scheduled_reboot/scheduled_reboot_dialog_unittest.cc",
+      "../browser/ui/ash/glanceables/glanceables_classroom_client_impl_unittest.cc",
       "../browser/ui/ash/glanceables/glanceables_keyed_service_factory_unittest.cc",
       "../browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc",
       "../browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc",
@@ -7802,7 +7808,7 @@
       "//chrome/browser/chromeos",
       "//chrome/browser/chromeos/launcher_search:search_util",
       "//chrome/browser/enterprise/connectors/device_trust/attestation/ash",
-      "//chrome/browser/metrics/structured",
+      "//chrome/browser/metrics/structured:features",
       "//chrome/browser/nearby_sharing:share_target",
       "//chrome/browser/nearby_sharing/certificates",
       "//chrome/browser/nearby_sharing/certificates:test_support",
@@ -8788,10 +8794,12 @@
       "../browser/browser_switcher/ieem_sitelist_parser_unittest.cc",
       "../browser/browser_switcher/mock_alternative_browser_driver.cc",
       "../browser/browser_switcher/mock_alternative_browser_driver.h",
+      "../browser/enterprise/connectors/device_trust/attestation/browser/browser_attestation_service_unittest.cc",
       "../browser/enterprise/connectors/device_trust/attestation/browser/device_attester_unittest.cc",
       "../browser/enterprise/connectors/device_trust/attestation/browser/profile_attester_unittest.cc",
       "../browser/enterprise/connectors/device_trust/attestation/desktop/desktop_attestation_service_unittest.cc",
       "../browser/enterprise/connectors/device_trust/browser/signing_key_policy_observer.cc",
+      "../browser/enterprise/profile_management/saml_response_parser_unittest.cc",
       "../browser/enterprise/profile_token_management/profile_token_navigation_throttle_unittest.cc",
       "../browser/enterprise/remote_commands/rotate_attestation_credential_job_unittest.cc",
       "../browser/enterprise/signals/user_delegate_impl_unittest.cc",
@@ -9146,6 +9154,7 @@
       "../browser/ui/views/toolbar/toolbar_action_view_unittest.cc",
       "../browser/ui/views/toolbar/toolbar_actions_bar_bubble_views_unittest.cc",
       "../browser/ui/views/toolbar/toolbar_button_unittest.cc",
+      "../browser/ui/views/toolbar/toolbar_ink_drop_util_unittest.cc",
       "../browser/ui/views/translate/partial_translate_bubble_view_unittest.cc",
       "../browser/ui/views/translate/translate_bubble_controller_unittest.cc",
       "../browser/ui/views/translate/translate_bubble_view_unittest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
index ae98c3b..ce9f675 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
@@ -29,7 +29,6 @@
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.DropdownItemViewInfo;
-import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdown;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdownAdapter;
 import org.chromium.chrome.browser.omnibox.suggestions.base.ActionChipsProperties;
@@ -38,6 +37,7 @@
 import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteResult;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.content_public.browser.test.util.KeyUtils;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
diff --git a/chrome/test/data/auth-basic-subframe.html b/chrome/test/data/auth-basic-subframe.html
new file mode 100644
index 0000000..051fa3d
--- /dev/null
+++ b/chrome/test/data/auth-basic-subframe.html
@@ -0,0 +1 @@
+<iframe id="subframe" src="auth-basic"></iframe>
diff --git a/chrome/test/data/extensions/api_test/autotest_private/test.js b/chrome/test/data/extensions/api_test/autotest_private/test.js
index 50ab7e2..9bfc880 100644
--- a/chrome/test/data/extensions/api_test/autotest_private/test.js
+++ b/chrome/test/data/extensions/api_test/autotest_private/test.js
@@ -594,6 +594,7 @@
       chrome.test.assertEq('Running', item.status);
       chrome.test.assertTrue(item.showsTooltip);
       chrome.test.assertFalse(item.pinnedByPolicy);
+      chrome.test.assertFalse(item.pinStateForcedByType);
       chrome.test.assertFalse(item.hasNotification);
     }));
   },
diff --git a/chrome/test/data/extensions/api_test/messaging/connect/page.js b/chrome/test/data/extensions/api_test/messaging/connect/page.js
index 8a2e600f..d54353e 100644
--- a/chrome/test/data/extensions/api_test/messaging/connect/page.js
+++ b/chrome/test/data/extensions/api_test/messaging/connect/page.js
@@ -147,6 +147,8 @@
 
 // For test sendMessage.
 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
-  chrome.test.assertEq({id: chrome.runtime.id, origin: 'null'}, sender);
+  const extensionOrigin = new URL(chrome.runtime.getURL('')).origin;
+  chrome.test.assertEq(
+      { id: chrome.runtime.id, origin: extensionOrigin }, sender);
   sendResponse({success: (request.step2 == 1)});
 });
diff --git a/chrome/test/data/extensions/api_test/service_worker/background/background.js b/chrome/test/data/extensions/api_test/service_worker/background/background.js
index a9a243e..c286dbfb 100644
--- a/chrome/test/data/extensions/api_test/service_worker/background/background.js
+++ b/chrome/test/data/extensions/api_test/service_worker/background/background.js
@@ -17,7 +17,7 @@
 };
 
 // Registers a service worker and stores it in registeredServiceWorker.
-// Intended to be called from content::ExecuteScript.
+// Intended to be called from content::ExecJs.
 test.registerServiceWorker = function(path) {
   return navigator.serviceWorker.register(path).then(function() {
     // Wait until the service worker is active.
diff --git a/chrome/test/data/extensions/api_test/tabs/send_message/echo.js b/chrome/test/data/extensions/api_test/tabs/send_message/echo.js
index aa99aa7..f2b356c4 100644
--- a/chrome/test/data/extensions/api_test/tabs/send_message/echo.js
+++ b/chrome/test/data/extensions/api_test/tabs/send_message/echo.js
@@ -5,7 +5,9 @@
 // A simple onMessage listener that responds to "ping" messages with a "pong"
 // message.
 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
-  const response =
-      request == 'ping' ? 'pong' : `Unexpected message: ${request}`;
+  const extensionOrigin = new URL(chrome.runtime.getURL('')).origin;
+  let response = 'pong';
+  if (request != 'ping' || sender.origin != extensionOrigin)
+    response = `Unexpected message "${request}" from origin "${sender.origin}"`;
   sendResponse(response);
 });
diff --git a/chrome/test/data/webui/chromeos/fake_network_config_mojom.js b/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
index ae8d607..6acdb1f 100644
--- a/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
+++ b/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
@@ -560,7 +560,7 @@
           result = OncMojo.getDefaultManagedProperties(
               foundState.type, foundState.guid, foundState.name);
         } else {
-          console.error('GUID not found: ' + guid);
+          console.warn('GUID not found: ' + guid);
         }
       }
       if (this.beforeGetManagedProperties) {
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts
index 006bce9..4185856a 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts
@@ -457,6 +457,62 @@
     assertFalse(editElement.hasError);
   });
 
+  test('AddAcceleratorMaximumAccelerators', async () => {
+    page = initShortcutCustomizationAppElement();
+    await flushTasks();
+
+    // Open dialog for first accelerator in second subsection.
+    await openDialogForAcceleratorInSubsection(1);
+    const editDialog = getPage().shadowRoot!.querySelector('#editDialog');
+    assertTrue(!!editDialog);
+
+    // Grab the first accelerator from second subsection.
+    const dialogAccels =
+        editDialog!.shadowRoot!.querySelector('cr-dialog')!.querySelectorAll(
+            'accelerator-edit-view');
+    // Expect only 1 accelerator initially.
+    assertEquals(1, dialogAccels!.length);
+
+    // Click on add button.
+    (editDialog!.shadowRoot!.querySelector('#addAcceleratorButton') as
+     CrButtonElement)
+        .click();
+
+    await flushTasks();
+
+    const editElement =
+        editDialog!.shadowRoot!.querySelector('#pendingAccelerator') as
+        AcceleratorEditViewElement;
+
+    // Assert no error has occurred prior to pressing a shortcut.
+    assertFalse(editElement.hasError);
+
+    const viewElement =
+        editElement!.shadowRoot!.querySelector('#acceleratorItem');
+
+    // Set the fake mojo return call.
+    const fakeResult: AcceleratorResultData = {
+      result: AcceleratorConfigResult.kMaximumAcceleratorsReached,
+      shortcutName: undefined,
+    };
+    provider.setFakeAddAcceleratorResult(fakeResult);
+
+    // Dispatch an add event, this should fail as it has a failure state.
+    viewElement!.dispatchEvent(new KeyboardEvent('keydown', {
+      key: ']',
+      keyCode: 221,
+      code: 'Key]',
+      ctrlKey: false,
+      altKey: true,
+      shiftKey: false,
+      metaKey: false,
+    }));
+
+    await flushTasks();
+
+    assertTrue(editElement.hasError);
+  });
+
   test('DisableDefaultAccelerator', async () => {
     page = initShortcutCustomizationAppElement();
     await flushTasks();
diff --git a/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc b/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc
index c5350440..4ec61354 100644
--- a/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc
+++ b/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc
@@ -262,7 +262,7 @@
   // Reload the page to trigger RenderFrameHost reuse. The API should remain
   // available.
   content::TestNavigationObserver observer(web_contents, 1);
-  EXPECT_TRUE(content::ExecuteScript(web_contents, "location.reload()"));
+  EXPECT_TRUE(content::ExecJs(web_contents, "location.reload()"));
   observer.Wait();
   EXPECT_EQ(true, content::EvalJs(
                       web_contents,
diff --git a/chrome/test/data/webui/mojo/mojo_js_interface_broker_browsertest.cc b/chrome/test/data/webui/mojo/mojo_js_interface_broker_browsertest.cc
index 8985029..2c9b329 100644
--- a/chrome/test/data/webui/mojo/mojo_js_interface_broker_browsertest.cc
+++ b/chrome/test/data/webui/mojo/mojo_js_interface_broker_browsertest.cc
@@ -288,7 +288,7 @@
   // Refresh to trigger a RenderFrame reuse.
   content::TestNavigationObserver observer(web_contents, 1);
   // TODO(https://crbug.com/1157718): migrate to ExecJs.
-  EXPECT_TRUE(content::ExecuteScript(web_contents, "location.reload()"));
+  EXPECT_TRUE(content::ExecJs(web_contents, "location.reload()"));
   observer.Wait();
 
   // Verify a new broker is created, and Foo still works.
@@ -373,7 +373,7 @@
 
   // Reload Bar iframe, Bar interface should still work.
   content::TestNavigationObserver observer(web_contents, 1);
-  EXPECT_TRUE(content::ExecuteScript(bar_frame, "location.reload()"));
+  EXPECT_TRUE(content::ExecJs(bar_frame, "location.reload()"));
   observer.Wait();
   bar_frame = ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
 
diff --git a/chrome/test/data/webui/new_tab_page/modules/history_clusters/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/history_clusters/module_test.ts
index 4c564748..5ea5c8a 100644
--- a/chrome/test/data/webui/new_tab_page/modules/history_clusters/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/history_clusters/module_test.ts
@@ -503,5 +503,39 @@
       assertTrue(!!moduleElement.cart);
       assertEquals(1, questTiles.length);
     });
+
+    test('Cart tile clicking metrics are collected', async () => {
+      loadTimeData.overrideValues({
+        modulesChromeCartInHistoryClustersModuleEnabled: true,
+      });
+
+      const cart: Cart = Object.assign({
+        domain: 'foo.com',
+        merchant: 'Foo',
+        cartUrl: {url: 'https://foo.com'},
+        productImageUrls: [],
+        discountText: '',
+        relativeDate: '6 mins ago',
+      });
+      const moduleElement = await initializeModule(
+          [createSampleCluster(HistoryClusterLayoutType.LAYOUT_1)], cart);
+
+      assertEquals(1, handler.getCallCount('getCartForCluster'));
+      assertTrue(!!moduleElement);
+      await waitAfterNextRender(moduleElement);
+      const cartTile = moduleElement.shadowRoot!.getElementById('cartTile');
+      assertTrue(!!cartTile);
+      assertTrue(!!moduleElement.cart);
+
+      // Act.
+      cartTile.click();
+
+      // Assert.
+      assertEquals(
+          1,
+          metrics.count(
+              `NewTabPage.HistoryClusters.Layout1.Click`,
+              HistoryClusterElementType.CART));
+    });
   });
 });
diff --git a/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts
index 45b9a721..c9aba4c5 100644
--- a/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts
@@ -16,8 +16,15 @@
 import {installMock} from '../../test_support.js';
 import {assertModuleHeaderTitle, createRelatedSearches, createSampleVisits} from '../history_clusters/test_support.js';
 
+function createSampleClusters(count: number): Cluster[] {
+  return new Array(count).fill(0).map(
+      (_, i) => createSampleCluster(2, {id: BigInt(i)}));
+}
+
 function createSampleCluster(
-    numRelatedSearches?: number, overrides?: Partial<Cluster>): Cluster {
+    numRelatedSearches: number,
+    overrides?: Partial<Cluster>,
+    ): Cluster {
   const cluster: Cluster = Object.assign(
       {
         id: BigInt(111),
@@ -46,43 +53,43 @@
   });
 
   async function initializeModule(clusters: Cluster[], cart: Cart|null = null):
-      Promise<HistoryClustersV2ModuleElement> {
+      Promise<HistoryClustersV2ModuleElement[]> {
     handler.setResultFor('getClusters', Promise.resolve({clusters}));
     handler.setResultFor('getCartForCluster', Promise.resolve({cart}));
-    const moduleElement = await historyClustersV2Descriptor.initialize(0) as
-        HistoryClustersV2ModuleElement;
-    await handler.whenCalled('getClusters');
-    document.body.append(moduleElement);
-    await waitAfterNextRender(moduleElement);
-    return moduleElement;
+    const moduleElements = await historyClustersV2Descriptor.initialize(0) as
+        HistoryClustersV2ModuleElement[];
+    if (moduleElements) {
+      moduleElements.forEach(element => {
+        document.body.append(element);
+      });
+    }
+
+    await waitAfterNextRender(document.body);
+    return moduleElements;
   }
 
   suite('core', () => {
     test('No module created if no history cluster data', async () => {
       // Arrange.
-      const moduleElement = await initializeModule([]);
+      const moduleElements = await initializeModule([]);
 
       // Assert.
-      assertEquals('', moduleElement.innerHTML);
+      assertEquals(null, moduleElements);
     });
 
-    test('No module created when data does not match layouts', async () => {
-      // Arrange.
-      const cluster: Partial<Cluster> = {
-        visits: createSampleVisits(2, 0),
-      };
-      const moduleElement =
-          await initializeModule([createSampleCluster(undefined, cluster)]);
-
-      // Assert.
-      assertEquals('', moduleElement.innerHTML);
+    test('Multiple module instances created successfully', async () => {
+      const instanceCount = 3;
+      const moduleElements =
+          await initializeModule(createSampleClusters(instanceCount));
+      assertEquals(instanceCount, moduleElements.length);
     });
 
     test('Header element populated with correct data', async () => {
       // Arrange.
       const sampleClusterLabel = '"Sample Journey"';
-      const moduleElement = await initializeModule(
-          [createSampleCluster(undefined, {label: sampleClusterLabel})]);
+      const moduleElements = await initializeModule(
+          [createSampleCluster(2, {label: sampleClusterLabel})]);
+      const moduleElement = moduleElements[0];
 
       // Assert.
       assertTrue(!!moduleElement);
@@ -95,8 +102,9 @@
     test('Header info button click opens info dialog', async () => {
       // Arrange.
       const sampleClusterLabel = '"Sample Journey"';
-      const moduleElement = await initializeModule(
-          [createSampleCluster(undefined, {label: sampleClusterLabel})]);
+      const moduleElements = await initializeModule(
+          [createSampleCluster(2, {label: sampleClusterLabel})]);
+      const moduleElement = moduleElements[0];
 
       // Act.
       assertTrue(!!moduleElement);
diff --git a/chrome/test/data/webui/password_manager/password_manager_interactive_ui_tests.js b/chrome/test/data/webui/password_manager/password_manager_interactive_ui_tests.js
index a1081b1..6350f74 100644
--- a/chrome/test/data/webui/password_manager/password_manager_interactive_ui_tests.js
+++ b/chrome/test/data/webui/password_manager/password_manager_interactive_ui_tests.js
@@ -32,6 +32,13 @@
   }
 };
 
-TEST_F('PasswordManagerUIFocusTest', 'All', function() {
+// https://crbug.com/1444623: Flaky on Mac.
+GEN('#if BUILDFLAG(IS_MAC)');
+GEN('#define MAYBE_All DISABLED_All');
+GEN('#else');
+GEN('#define MAYBE_All All');
+GEN('#endif');
+
+TEST_F('PasswordManagerUIFocusTest', 'MAYBE_All', function() {
   mocha.run();
 });
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index a026869..3ff584f 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -46,11 +46,9 @@
     "fake_system_display.js",
     "fake_user_action_recorder.js",
     "fake_users_private.js",
-    "fingerprint_list_subpage_test.js",
     "hotspot_config_dialog_tests.js",
     "hotspot_subpage_tests.js",
     "hotspot_summary_item_tests.js",
-    "google_assistant_subpage_test.js",
     "input_page_test.js",
     "internet_config_test.js",
     "internet_detail_menu_test.js",
@@ -172,7 +170,7 @@
     "internet_page/test_internet_page_browser_proxy.ts",
     "internet_page/tether_connection_dialog_test.ts",
 
-    "kerberos_page/kerberos_accounts_test.js",
+    "kerberos_page/kerberos_accounts_subpage_test.js",
     "kerberos_page/kerberos_page_test.ts",
     "kerberos_page/test_kerberos_accounts_browser_proxy.ts",
 
@@ -228,6 +226,8 @@
     "os_languages_page/smart_inputs_page_test.ts",
 
     "os_people_page/add_user_dialog_test.ts",
+    "os_people_page/fingerprint_list_subpage_test.ts",
+    "os_people_page/test_fingerprint_browser_proxy.ts",
 
     "os_printing_page/os_printing_page_test.ts",
 
@@ -237,6 +237,7 @@
     "os_privacy_page/test_metrics_consent_browser_proxy.ts",
     "os_privacy_page/test_privacy_hub_browser_proxy.ts",
 
+    "os_search_page/google_assistant_subpage_test.ts",
     "os_search_page/os_search_page_test.ts",
     "os_search_page/search_subpage_test.ts",
 
diff --git a/chrome/test/data/webui/settings/chromeos/apn_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/apn_subpage_tests.js
index 5f6495a..ffde4e2 100644
--- a/chrome/test/data/webui/settings/chromeos/apn_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/apn_subpage_tests.js
@@ -199,4 +199,47 @@
     assertEquals(1, counter);
     Router.getInstance().navigateToPreviousRoute = navigateToPreviousRoute;
   });
+
+  test('Network removed while on subpage', async function() {
+    let counter = 0;
+    const navigateToPreviousRoute =
+        Router.getInstance().navigateToPreviousRoute;
+    Router.getInstance().navigateToPreviousRoute = () => {
+      counter++;
+    };
+
+    // Simulate the network being removed.
+    mojoApi_.resetForTest();
+    apnSubpage.onNetworkStateChanged(
+        OncMojo.getDefaultNetworkState(NetworkType.kCellular, 'cellular'));
+    await flushTasks();
+
+    assertEquals(1, counter);
+    Router.getInstance().navigateToPreviousRoute = navigateToPreviousRoute;
+  });
+
+  test('Network removed while not on subpage', async function() {
+    // Navigate to a different page.
+    const params = new URLSearchParams();
+    params.append('type', OncMojo.getNetworkTypeString(NetworkType.kCellular));
+    Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
+    await flushTasks();
+    assertEquals(routes.INTERNET_NETWORKS, Router.getInstance().currentRoute);
+
+    let counter = 0;
+    const navigateToPreviousRoute =
+        Router.getInstance().navigateToPreviousRoute;
+    Router.getInstance().navigateToPreviousRoute = () => {
+      counter++;
+    };
+
+    // Simulate the network being removed.
+    mojoApi_.resetForTest();
+    apnSubpage.onNetworkStateChanged(
+        OncMojo.getDefaultNetworkState(NetworkType.kCellular, 'cellular'));
+    await flushTasks();
+
+    assertEquals(0, counter);
+    Router.getInstance().navigateToPreviousRoute = navigateToPreviousRoute;
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
index 7ca7ca6..9a59b0d 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
@@ -95,14 +95,16 @@
 
 suite('CrostiniPageTests', function() {
   setup(function() {
+    loadTimeData.overrideValues({
+      isCrostiniAllowed: true,
+      isCrostiniSupported: true,
+    });
     crostiniBrowserProxy = new TestCrostiniBrowserProxy();
     CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
     guestOsBrowserProxy = new TestGuestOsBrowserProxy();
     GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
     PolymerTest.clearBody();
     crostiniPage = document.createElement('settings-crostini-page');
-    crostiniPage.showCrostini = true;
-    crostiniPage.allowCrostini = true;
     document.body.appendChild(crostiniPage);
     testing.Test.disableAnimationsAndTransitions();
   });
@@ -167,8 +169,8 @@
     });
 
     test('NotSupported', function() {
-      crostiniPage.showCrostini = false;
-      crostiniPage.allowCrostini = false;
+      crostiniPage.set('isCrostiniSupported_', false);
+      crostiniPage.set('isCrostiniAllowed_', false);
       flush();
       assertTrue(!!crostiniPage.shadowRoot.querySelector('#enable'));
       assertFalse(
@@ -176,8 +178,8 @@
     });
 
     test('NotAllowed', function() {
-      crostiniPage.showCrostini = true;
-      crostiniPage.allowCrostini = false;
+      crostiniPage.set('isCrostiniSupported_', true);
+      crostiniPage.set('isCrostiniAllowed_', false);
       flush();
       assertTrue(!!crostiniPage.shadowRoot.querySelector('#enable'));
       assertTrue(
diff --git a/chrome/test/data/webui/settings/chromeos/fingerprint_list_subpage_test.js b/chrome/test/data/webui/settings/chromeos/fingerprint_list_subpage_test.js
deleted file mode 100644
index 2348a123a..0000000
--- a/chrome/test/data/webui/settings/chromeos/fingerprint_list_subpage_test.js
+++ /dev/null
@@ -1,420 +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.
-
-import 'chrome://os-settings/chromeos/lazy_load.js';
-
-import {FingerprintBrowserProxyImpl, FingerprintResultType, FingerprintSetupStep} from 'chrome://os-settings/chromeos/lazy_load.js';
-import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
-import {webUIListenerCallback} from 'chrome://resources/ash/common/cr.m.js';
-import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
-import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
-import {isVisible} from 'chrome://webui-test/test_util.js';
-
-/** @implements {FingerprintBrowserProxy} */
-class TestFingerprintBrowserProxy extends TestBrowserProxy {
-  constructor() {
-    super([
-      'getFingerprintsList',
-      'getNumFingerprints',
-      'startEnroll',
-      'cancelCurrentEnroll',
-      'getEnrollmentLabel',
-      'removeEnrollment',
-      'changeEnrollmentLabel',
-    ]);
-
-    /** @private {!Array<string>} */
-    this.fingerprintsList_ = [];
-  }
-
-  /** @param {!Array<string>} fingerprints */
-  setFingerprints(fingerprints) {
-    this.fingerprintsList_ = fingerprints.slice();
-  }
-
-  /**
-   * @param {FingerprintResultType} result
-   * @param {boolean} complete
-   * @param {number} percent
-   */
-  scanReceived(result, complete, percent) {
-    if (complete) {
-      this.fingerprintsList_.push('New Label');
-    }
-
-    webUIListenerCallback(
-        'on-fingerprint-scan-received',
-        {result: result, isComplete: complete, percentComplete: percent});
-  }
-
-  /** @override */
-  getFingerprintsList() {
-    this.methodCalled('getFingerprintsList');
-    /** @type {FingerprintInfo} */
-    const fingerprintInfo = {
-      fingerprintsList: this.fingerprintsList_.slice(),
-      isMaxed: this.fingerprintsList_.length >= 3,
-    };
-    return Promise.resolve(fingerprintInfo);
-  }
-
-  /** @override */
-  getNumFingerprints() {
-    this.methodCalled('getNumFingerprints');
-    return Promise.resolve(fingerprintsList_.length);
-  }
-
-  /** @override */
-  startEnroll() {
-    this.methodCalled('startEnroll');
-  }
-
-  /** @override */
-  cancelCurrentEnroll() {
-    this.methodCalled('cancelCurrentEnroll');
-  }
-
-  /** @override */
-  getEnrollmentLabel(index) {
-    this.methodCalled('getEnrollmentLabel');
-    return Promise.resolve(this.fingerprintsList_[index]);
-  }
-
-  /** @override */
-  removeEnrollment(index) {
-    this.fingerprintsList_.splice(index, 1);
-    this.methodCalled('removeEnrollment', index);
-    return Promise.resolve(true);
-  }
-
-  /** @override */
-  changeEnrollmentLabel(index, newLabel) {
-    this.fingerprintsList_[index] = newLabel;
-    this.methodCalled('changeEnrollmentLabel', index, newLabel);
-    return Promise.resolve(true);
-  }
-}
-
-suite('settings-fingerprint-list-subpage', function() {
-  /** @type {?SettingsFingerprintListElement} */
-  let fingerprintList = null;
-
-  /** @type {?SettingsSetupFingerprintDialogElement} */
-  let dialog = null;
-  /** @type {?HTMLButtonElement} */
-  let addAnotherButton = null;
-  /** @type {?settings.TestFingerprintBrowserProxy} */
-  let browserProxy = null;
-
-  /**
-   * @param {number} index
-   * @param {string=} opt_label
-   */
-  function createFakeEvent(index, opt_label) {
-    return {model: {index: index, item: opt_label || ''}};
-  }
-
-  function openDialog() {
-    fingerprintList.shadowRoot.querySelector('.action-button').click();
-    flush();
-    dialog = fingerprintList.shadowRoot.querySelector(
-        'settings-setup-fingerprint-dialog');
-    addAnotherButton = dialog.shadowRoot.querySelector('#addAnotherButton');
-  }
-
-  setup(async function() {
-    browserProxy = new TestFingerprintBrowserProxy();
-    FingerprintBrowserProxyImpl.setInstanceForTesting(browserProxy);
-
-    PolymerTest.clearBody();
-    fingerprintList =
-        document.createElement('settings-fingerprint-list-subpage');
-    document.body.appendChild(fingerprintList);
-    flush();
-    await browserProxy.whenCalled('getFingerprintsList');
-    assertEquals(0, fingerprintList.fingerprints_.length);
-    browserProxy.resetResolver('getFingerprintsList');
-  });
-
-  test('EnrollingFingerprintLottieAnimation', async function() {
-    loadTimeData.overrideValues({fingerprintUnlockEnabled: true});
-    openDialog();
-    await browserProxy.whenCalled('startEnroll');
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.step_);
-    assertFalse(
-        dialog.shadowRoot.querySelector('#scannerLocationLottie').hidden);
-  });
-
-  // Verify running through the enroll session workflow
-  // (settings-setup-fingerprint-dialog) works as expected.
-  test('EnrollingFingerprint', async function() {
-    loadTimeData.overrideValues({fingerprintUnlockEnabled: true});
-    openDialog();
-    await browserProxy.whenCalled('startEnroll');
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertEquals(0, dialog.percentComplete_);
-    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.step_);
-    assertFalse(
-        dialog.shadowRoot.querySelector('#scannerLocationLottie').hidden);
-    assertTrue(dialog.shadowRoot.querySelector('#arc').hidden);
-    // Message should be shown for LOCATE_SCANNER step.
-    assertEquals(
-        'visible',
-        window.getComputedStyle(dialog.shadowRoot.querySelector('#messageDiv'))
-            .visibility);
-
-    // First tap on the sensor to start fingerprint enrollment.
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 20 /* percent */);
-    assertEquals(20, dialog.percentComplete_);
-    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.step_);
-    assertTrue(
-        dialog.shadowRoot.querySelector('#scannerLocationLottie').hidden);
-    assertFalse(dialog.shadowRoot.querySelector('#arc').hidden);
-
-    // Verify that by sending a scan problem, the div that contains the
-    // problem message should be visible.
-    browserProxy.scanReceived(
-        FingerprintResultType.TOO_FAST, false, 20 /* percent */);
-    assertEquals(20, dialog.percentComplete_);
-    assertEquals(
-        'visible',
-        window.getComputedStyle(dialog.shadowRoot.querySelector('#messageDiv'))
-            .visibility);
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 50 /* percent */);
-    assertEquals(
-        'hidden',
-        window.getComputedStyle(dialog.shadowRoot.querySelector('#messageDiv'))
-            .visibility);
-    assertEquals(50, dialog.percentComplete_);
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 70 /* percent */);
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, true, 100 /* percent */);
-    assertEquals(FingerprintSetupStep.READY, dialog.step_);
-    // Message should be shown for READY step.
-    assertEquals(
-        'visible',
-        window.getComputedStyle(dialog.shadowRoot.querySelector('#messageDiv'))
-            .visibility);
-
-    // Verify that by tapping the continue button we should exit the dialog
-    // and the fingerprint list should have one fingerprint registered.
-    dialog.shadowRoot.querySelector('#closeButton').click();
-    await flushTasks();
-    await browserProxy.whenCalled('getFingerprintsList');
-    assertEquals(1, fingerprintList.fingerprints_.length);
-  });
-
-  // Verify enrolling a fingerprint, then enrolling another without closing the
-  // dialog works as intended.
-  test('EnrollingAnotherFingerprint', async function() {
-    loadTimeData.overrideValues({fingerprintUnlockEnabled: true});
-    openDialog();
-    await browserProxy.whenCalled('startEnroll');
-    browserProxy.resetResolver('startEnroll');
-
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertEquals(0, dialog.percentComplete_);
-    assertFalse(isVisible(addAnotherButton));
-    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.step_);
-
-    // First tap on the sensor to start fingerprint enrollment.
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 20 /* percent */);
-    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.step_);
-
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, true, 100 /* percent */);
-    assertEquals(FingerprintSetupStep.READY, dialog.step_);
-
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertTrue(isVisible(addAnotherButton));
-    addAnotherButton.click();
-
-    // Once the first fingerprint is enrolled, verify that enrolling the
-    // second fingerprint without closing the dialog works as expected.
-    await Promise.all([
-      browserProxy.whenCalled('startEnroll'),
-      browserProxy.whenCalled('getFingerprintsList'),
-    ]);
-    browserProxy.resetResolver('getFingerprintsList');
-
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertFalse(isVisible(addAnotherButton));
-    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.step_);
-    assertTrue(
-        dialog.shadowRoot.querySelector('#scannerLocationLottie').hidden);
-    assertFalse(dialog.shadowRoot.querySelector('#arc').hidden);
-
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, true, 100 /* percent */);
-
-    // Verify that by tapping the continue button we should exit the
-    // dialog and the fingerprint list should have two fingerprints
-    // registered.
-    dialog.shadowRoot.querySelector('#closeButton').click();
-    await browserProxy.whenCalled('getFingerprintsList');
-    assertEquals(2, fingerprintList.fingerprints_.length);
-  });
-
-  // Verify after third fingerprint is enrolled, add another button in the
-  // setup dialog is hidden.
-  test('EnrollingThirdFingerprint', async function() {
-    browserProxy.setFingerprints(['1', '2']);
-    fingerprintList.updateFingerprintsList_();
-
-    openDialog();
-    await browserProxy.whenCalled('startEnroll');
-    browserProxy.resetResolver('startEnroll');
-
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertEquals(0, dialog.percentComplete_);
-    assertFalse(isVisible(addAnotherButton));
-    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.step_);
-
-    // First tap on the sensor to start fingerprint enrollment.
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 20 /* percent */);
-    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.step_);
-
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, true, 100 /* percent */);
-    assertEquals(FingerprintSetupStep.READY, dialog.step_);
-    await browserProxy.whenCalled('getFingerprintsList');
-    browserProxy.resetResolver('getFingerprintsList');
-
-    // Add another is hidden after third fingerprint is enrolled.
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertFalse(isVisible(addAnotherButton));
-    assertEquals(3, fingerprintList.fingerprints_.length);
-  });
-
-  test('CancelEnrollingFingerprint', async function() {
-    openDialog();
-    await browserProxy.whenCalled('startEnroll');
-    assertTrue(dialog.shadowRoot.querySelector('#dialog').open);
-    assertEquals(0, dialog.percentComplete_);
-    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.step_);
-    // First tap on the sensor to start fingerprint enrollment.
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 20 /* percent */);
-    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.step_);
-
-    browserProxy.scanReceived(
-        FingerprintResultType.SUCCESS, false, 30 /* percent */);
-    assertEquals(30, dialog.percentComplete_);
-    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.step_);
-
-    // Verify that by tapping the exit button we should exit the dialog
-    // and the fingerprint list should have zero fingerprints registered.
-    dialog.shadowRoot.querySelector('#closeButton').click();
-    await browserProxy.whenCalled('cancelCurrentEnroll');
-    assertEquals(0, fingerprintList.fingerprints_.length);
-  });
-
-  test('RemoveFingerprint', async function() {
-    browserProxy.setFingerprints(['Label 1', 'Label 2']);
-    fingerprintList.updateFingerprintsList_();
-
-    await browserProxy.whenCalled('getFingerprintsList');
-    browserProxy.resetResolver('getFingerprintsList');
-    assertEquals(2, fingerprintList.fingerprints_.length);
-    fingerprintList.onFingerprintDeleteTapped_(createFakeEvent(0));
-
-    await Promise.all([
-      browserProxy.whenCalled('removeEnrollment'),
-      browserProxy.whenCalled('getFingerprintsList'),
-    ]);
-    assertEquals(1, fingerprintList.fingerprints_.length);
-  });
-
-  test('Deep link to add fingerprint', async () => {
-    const settingId = '1111';
-
-    browserProxy.setFingerprints(['Label 1', 'Label 2']);
-    fingerprintList.updateFingerprintsList_();
-    await browserProxy.whenCalled('getFingerprintsList');
-
-    const params = new URLSearchParams();
-    params.append('settingId', settingId);
-    Router.getInstance().navigateTo(routes.FINGERPRINT, params);
-
-    flush();
-
-    const deepLinkElement =
-        fingerprintList.shadowRoot.querySelector('#addFingerprint');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Add button should be focused for settingId=' + settingId);
-  });
-
-  test('Deep link to remove fingerprint', async () => {
-    const settingId = '1112';
-
-    browserProxy.setFingerprints(['Label 1', 'Label 2']);
-    fingerprintList.updateFingerprintsList_();
-    await browserProxy.whenCalled('getFingerprintsList');
-
-    const params = new URLSearchParams();
-    params.append('settingId', settingId);
-    Router.getInstance().navigateTo(routes.FINGERPRINT, params);
-
-    flush();
-
-    const deepLinkElement =
-        fingerprintList.root.querySelectorAll('cr-icon-button')[0];
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Trash can button should be focused for settingId=' + settingId);
-  });
-
-  test('ChangeFingerprintLabel', async function() {
-    browserProxy.setFingerprints(['Label 1']);
-    fingerprintList.updateFingerprintsList_();
-
-    await browserProxy.whenCalled('getFingerprintsList');
-    assertEquals(1, fingerprintList.fingerprints_.length);
-    assertEquals('Label 1', fingerprintList.fingerprints_[0]);
-
-    // Verify that by sending a fingerprint input change event, the new
-    // label gets changed as expected.
-    fingerprintList.onFingerprintLabelChanged_(
-        createFakeEvent(0, 'New Label 1'));
-
-    await Promise.all([
-      browserProxy.whenCalled('changeEnrollmentLabel'),
-      browserProxy.whenCalled('getFingerprintsList'),
-    ]);
-    assertEquals('New Label 1', fingerprintList.fingerprints_[0]);
-  });
-
-  test('AddingNewFingerprint', async function() {
-    browserProxy.setFingerprints(['1', '2', '3']);
-    fingerprintList.updateFingerprintsList_();
-
-    // Verify that new fingerprints cannot be added when there are already three
-    // registered fingerprints.
-    await browserProxy.whenCalled('getFingerprintsList');
-    browserProxy.resetResolver('getFingerprintsList');
-    assertEquals(3, fingerprintList.fingerprints_.length);
-    assertTrue(
-        fingerprintList.shadowRoot.querySelector('.action-button').disabled);
-    fingerprintList.onFingerprintDeleteTapped_(createFakeEvent(0));
-
-    await Promise.all([
-      browserProxy.whenCalled('removeEnrollment'),
-      browserProxy.whenCalled('getFingerprintsList'),
-    ]);
-    assertEquals(2, fingerprintList.fingerprints_.length);
-    assertFalse(
-        fingerprintList.shadowRoot.querySelector('.action-button').disabled);
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/google_assistant_subpage_test.js b/chrome/test/data/webui/settings/chromeos/google_assistant_subpage_test.js
deleted file mode 100644
index e386815..0000000
--- a/chrome/test/data/webui/settings/chromeos/google_assistant_subpage_test.js
+++ /dev/null
@@ -1,485 +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.
-
-import 'chrome://os-settings/chromeos/lazy_load.js';
-
-import {ConsentStatus, DspHotwordState, GoogleAssistantBrowserProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
-import {CrSettingsPrefs, Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
-import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
-import {TestMock} from 'chrome://webui-test/test_mock.js';
-
-suite('GoogleAssistantHandler', function() {
-  /** @type {SettingsGoogleAssistantSubpageElement} */
-  let page = null;
-
-  let browserProxy = null;
-
-  suiteSetup(function() {
-    loadTimeData.overrideValues({
-      isAssistantAllowed: true,
-      hotwordDspAvailable: true,
-    });
-  });
-
-  setup(function() {
-    browserProxy = TestMock.fromClass(GoogleAssistantBrowserProxyImpl);
-    GoogleAssistantBrowserProxyImpl.setInstanceForTesting(browserProxy);
-
-    PolymerTest.clearBody();
-
-    const prefElement = document.createElement('settings-prefs');
-    document.body.appendChild(prefElement);
-
-    return CrSettingsPrefs.initialized.then(function() {
-      page = document.createElement('settings-google-assistant-subpage');
-      page.prefs = prefElement.prefs;
-      document.body.appendChild(page);
-    });
-  });
-
-  teardown(function() {
-    page.remove();
-  });
-
-  test('toggleAssistant', function() {
-    flush();
-    const button = page.shadowRoot.querySelector('#google-assistant-enable');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertFalse(button.checked);
-
-    // Tap the enable toggle button and ensure the state becomes enabled.
-    button.click();
-    flush();
-    assertTrue(button.checked);
-  });
-
-  test('toggleAssistantContext', function() {
-    let button =
-        page.shadowRoot.querySelector('#google-assistant-context-enable');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.setPrefValue('settings.voice_interaction.context.enabled', false);
-    flush();
-    button = page.shadowRoot.querySelector('#google-assistant-context-enable');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertFalse(button.checked);
-
-    button.click();
-    flush();
-    assertTrue(button.checked);
-    assertTrue(
-        page.getPref('settings.voice_interaction.context.enabled.value'));
-  });
-
-  test('toggleAssistantHotword', async function() {
-    let button =
-        page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
-    flush();
-    button = page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertFalse(button.checked);
-
-    button.click();
-    flush();
-    assertTrue(button.checked);
-    assertTrue(
-        page.getPref('settings.voice_interaction.hotword.enabled.value'));
-    await browserProxy.whenCalled('syncVoiceModelStatus');
-  });
-
-  test('hotwordToggleVisibility', function() {
-    let button =
-        page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    assertFalse(!!button);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    button = page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    assertTrue(!!button);
-  });
-
-  test('hotwordToggleDisabledForChildUser', function() {
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.set('prefs.settings.voice_interaction.hotword.enabled', {
-      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
-      controlledBy: chrome.settingsPrivate.ControlledBy.CHILD_RESTRICTION,
-      value: false,
-    });
-
-    flush();
-    const button =
-        page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    const indicator =
-        page.shadowRoot.querySelector('#google-assistant-hotword-enable')
-            .shadowRoot.querySelector('cr-policy-pref-indicator');
-    assertTrue(!!button);
-    assertTrue(!!indicator);
-    assertTrue(button.disabled);
-  });
-
-  test('tapOnRetrainVoiceModel', async function() {
-    let button = page.shadowRoot.querySelector('#retrain-voice-model');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
-    page.setPrefValue(
-        'settings.voice_interaction.activity_control.consent_status',
-        ConsentStatus.ACTIVITY_CONTROL_ACCEPTED);
-    flush();
-    button = page.shadowRoot.querySelector('#retrain-voice-model');
-    assertTrue(!!button);
-
-    button.click();
-    flush();
-    await browserProxy.whenCalled('retrainAssistantVoiceModel');
-  });
-
-  test('retrainButtonVisibility', function() {
-    let button = page.shadowRoot.querySelector('#retrain-voice-model');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-    button = page.shadowRoot.querySelector('#retrain-voice-model');
-    assertFalse(!!button);
-
-    // Hotword disabled.
-    // Button should not be shown.
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
-    flush();
-    button = page.shadowRoot.querySelector('#retrain-voice-model');
-    assertFalse(!!button);
-
-    // Hotword enabled.
-    // Button should be shown.
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
-    flush();
-    button = page.shadowRoot.querySelector('#retrain-voice-model');
-    assertTrue(!!button);
-  });
-
-  test('Deep link to retrain voice model', async () => {
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
-    page.setPrefValue(
-        'settings.voice_interaction.activity_control.consent_status',
-        ConsentStatus.ACTIVITY_CONTROL_ACCEPTED);
-    flush();
-
-    const params = new URLSearchParams();
-    params.append('settingId', '607');
-    Router.getInstance().navigateTo(routes.GOOGLE_ASSISTANT, params);
-
-    const deepLinkElement =
-        page.shadowRoot.querySelector('#retrain-voice-model')
-            .shadowRoot.querySelector('cr-button');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Retrain model button should be focused for settingId=607.');
-  });
-
-  test('toggleAssistantNotification', function() {
-    let button =
-        page.shadowRoot.querySelector('#google-assistant-notification-enable');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.setPrefValue('settings.voice_interaction.notification.enabled', false);
-    flush();
-    button =
-        page.shadowRoot.querySelector('#google-assistant-notification-enable');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertFalse(button.checked);
-
-    button.click();
-    flush();
-    assertTrue(button.checked);
-    assertTrue(
-        page.getPref('settings.voice_interaction.notification.enabled.value'));
-  });
-
-  test('toggleAssistantLaunchWithMicOpen', function() {
-    let button =
-        page.shadowRoot.querySelector('#google-assistant-launch-with-mic-open');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.setPrefValue('settings.voice_interaction.launch_with_mic_open', false);
-    flush();
-    button =
-        page.shadowRoot.querySelector('#google-assistant-launch-with-mic-open');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertFalse(button.checked);
-
-    button.click();
-    flush();
-    assertTrue(button.checked);
-    assertTrue(
-        page.getPref('settings.voice_interaction.launch_with_mic_open.value'));
-  });
-
-  test('tapOnAssistantSettings', async function() {
-    let button = page.shadowRoot.querySelector('#google-assistant-settings');
-    assertFalse(!!button);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-    button = page.shadowRoot.querySelector('#google-assistant-settings');
-    assertTrue(!!button);
-
-    button.click();
-    flush();
-    await browserProxy.whenCalled('showGoogleAssistantSettings');
-  });
-
-  test('assistantDisabledByPolicy', function() {
-    let button = page.shadowRoot.querySelector('#google-assistant-enable');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertFalse(button.checked);
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-    button = page.shadowRoot.querySelector('#google-assistant-enable');
-    assertTrue(!!button);
-    assertFalse(button.disabled);
-    assertTrue(button.checked);
-
-    page.setPrefValue('settings.assistant.disabled_by_policy', true);
-    flush();
-    assertTrue(!!button);
-    assertTrue(button.disabled);
-    assertFalse(button.checked);
-  });
-});
-
-suite('GoogleAssistantHandlerWithNoDspHotword', function() {
-  /** @type {SettingsGoogleAssistantSubpageElement} */
-  let page = null;
-
-  let browserProxy = null;
-
-  suiteSetup(function() {
-    loadTimeData.overrideValues({
-      isAssistantAllowed: true,
-      hotwordDspAvailable: false,
-    });
-  });
-
-  setup(function() {
-    browserProxy = TestMock.fromClass(GoogleAssistantBrowserProxyImpl);
-    GoogleAssistantBrowserProxyImpl.setInstanceForTesting(browserProxy);
-
-    PolymerTest.clearBody();
-
-    const prefElement = document.createElement('settings-prefs');
-    document.body.appendChild(prefElement);
-
-    return CrSettingsPrefs.initialized.then(function() {
-      page = document.createElement('settings-google-assistant-subpage');
-      page.prefs = prefElement.prefs;
-      document.body.appendChild(page);
-      flush();
-    });
-  });
-
-  teardown(function() {
-    page.remove();
-  });
-
-  /**
-   * @param {!HTMLElement} select
-   * @param {!value} string
-   */
-  function selectValue(select, value) {
-    select.value = value;
-    select.dispatchEvent(new CustomEvent('change'));
-    flush();
-  }
-
-  test('hotwordToggleVisibilityWithNoDspHotword', function() {
-    let toggle =
-        page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    assertFalse(!!toggle);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    toggle = page.shadowRoot.querySelector('#google-assistant-hotword-enable');
-    assertFalse(!!toggle);
-  });
-
-  test('dspHotwordDropdownVisibilityWithNoDspHotword', function() {
-    let container = page.shadowRoot.querySelector('#dsp-hotword-container');
-    assertFalse(!!container);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    container = page.shadowRoot.querySelector('#dsp-hotword-container');
-    assertTrue(!!container);
-  });
-
-  test('dspHotwordDropdownIndicatorEnabled', function() {
-    let indicator =
-        page.shadowRoot.querySelector('#hotword-policy-pref-indicator');
-    assertFalse(!!indicator);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.set('prefs.settings.voice_interaction.hotword.enabled', {
-      enforcement: chrome.settingsPrivate.Enforcement.RECOMMENDED,
-      value: true,
-    });
-
-    flush();
-    const dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    indicator = page.shadowRoot.querySelector('#hotword-policy-pref-indicator');
-    assertTrue(!!dropdown);
-    assertFalse(!!indicator);
-    assertFalse(dropdown.hasAttribute('disabled'));
-  });
-
-  test('dspHotwordDropdownIndicatorDisabled', function() {
-    let indicator =
-        page.shadowRoot.querySelector('#hotword-policy-pref-indicator');
-    assertFalse(!!indicator);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.set('prefs.settings.voice_interaction.hotword.enabled', {
-      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
-      value: true,
-    });
-
-    flush();
-    const dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    indicator = page.shadowRoot.querySelector('#hotword-policy-pref-indicator');
-    assertTrue(!!dropdown);
-    assertTrue(!!indicator);
-    assertTrue(dropdown.hasAttribute('disabled'));
-  });
-
-  test('dspHotwordDropdownDisabledForChildUser', function() {
-    let indicator =
-        page.shadowRoot.querySelector('#hotword-policy-pref-indicator');
-    assertFalse(!!indicator);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    page.set('prefs.settings.voice_interaction.hotword.enabled', {
-      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
-      controlledBy: chrome.settingsPrivate.ControlledBy.CHILD_RESTRICTION,
-      value: false,
-    });
-
-    flush();
-    const dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    indicator = page.shadowRoot.querySelector('#hotword-policy-pref-indicator');
-    assertTrue(!!dropdown);
-    assertTrue(!!indicator);
-    assertTrue(dropdown.disabled);
-  });
-
-  test('dspHotwordDropdownSelection', function() {
-    let dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertFalse(!!dropdown);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertTrue(!!dropdown);
-    assertFalse(dropdown.disabled);
-
-    selectValue(dropdown, DspHotwordState.DEFAULT_ON);
-    flush();
-    assertTrue(
-        page.getPref('settings.voice_interaction.hotword.enabled.value'));
-    assertFalse(
-        page.getPref('settings.voice_interaction.hotword.always_on.value'));
-
-    selectValue(dropdown, DspHotwordState.ALWAYS_ON);
-    flush();
-    assertTrue(
-        page.getPref('settings.voice_interaction.hotword.enabled.value'));
-    assertTrue(
-        page.getPref('settings.voice_interaction.hotword.always_on.value'));
-
-    selectValue(dropdown, DspHotwordState.OFF);
-    flush();
-    assertFalse(
-        page.getPref('settings.voice_interaction.hotword.enabled.value'));
-    assertFalse(
-        page.getPref('settings.voice_interaction.hotword.always_on.value'));
-  });
-
-  test('dspHotwordDropdownStatus', function() {
-    let dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertFalse(!!dropdown);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertTrue(!!dropdown);
-    assertFalse(dropdown.disabled);
-
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
-    page.setPrefValue('settings.voice_interaction.hotword.always_on', false);
-    flush();
-    assertEquals(Number(dropdown.value), DspHotwordState.DEFAULT_ON);
-
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
-    page.setPrefValue('settings.voice_interaction.hotword.always_on', true);
-    flush();
-    assertEquals(Number(dropdown.value), DspHotwordState.ALWAYS_ON);
-
-    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
-    page.setPrefValue('settings.voice_interaction.hotword.always_on', false);
-    flush();
-    assertEquals(Number(dropdown.value), DspHotwordState.OFF);
-  });
-
-  test('dspHotwordDropdownDefaultOnSync', async function() {
-    let dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertFalse(!!dropdown);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertTrue(!!dropdown);
-    assertFalse(dropdown.disabled);
-    selectValue(dropdown, DspHotwordState.OFF);
-    flush();
-
-    selectValue(dropdown, DspHotwordState.DEFAULT_ON);
-    flush();
-    await browserProxy.whenCalled('syncVoiceModelStatus');
-  });
-
-  test('dspHotwordDropdownAlwaysOnSync', async function() {
-    let dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertFalse(!!dropdown);
-
-    page.setPrefValue('settings.voice_interaction.enabled', true);
-    flush();
-
-    dropdown = page.shadowRoot.querySelector('#dsp-hotword-state');
-    assertTrue(!!dropdown);
-    assertFalse(dropdown.disabled);
-    selectValue(dropdown, DspHotwordState.OFF);
-    flush();
-
-    selectValue(dropdown, DspHotwordState.ALWAYS_ON);
-    flush();
-    await browserProxy.whenCalled('syncVoiceModelStatus');
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js
index e50474d4..c9c41b4 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js
@@ -569,6 +569,14 @@
       assertEquals(
           deepLinkElement, getDeepActiveElement(),
           'Allow shared proxy toggle should be focused for settingId=11.');
+
+      // Close the page to ensure the test is fully cleaned up and wait for
+      // os_route's popstate listener to fire. If we don't add this wait, this
+      // event can fire during the other tests which may interfere with its
+      // routing.
+      const popStatePromise = eventToPromise('popstate', window);
+      internetDetailPage.close();
+      await popStatePromise;
     });
 
     test('WiFi page disabled when blocked by policy', async () => {
@@ -1597,6 +1605,58 @@
         }
       });
     });
+
+    test('Cellular network not found while in detail subpage', async () => {
+      init();
+      mojoApi_.setNetworkTypeEnabledState(NetworkType.kCellular, true);
+
+      // Simulate navigating to mobile data subpage.
+      let params = new URLSearchParams();
+      params.append(
+          'type', OncMojo.getNetworkTypeString(NetworkType.kCellular));
+      Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
+      assertEquals(routes.INTERNET_NETWORKS, Router.getInstance().currentRoute);
+      await flushAsync();
+
+      // Navigate to cellular detail page. Because the network is not found, the
+      // page should navigate backwards.
+      const popStatePromise = eventToPromise('popstate', window);
+      params = new URLSearchParams();
+      params.append('guid', 'cellular_guid');
+      params.append('type', 'Cellular');
+      params.append('name', 'cellular');
+      Router.getInstance().navigateTo(routes.NETWORK_DETAIL, params);
+
+      await popStatePromise;
+      assertEquals(routes.INTERNET_NETWORKS, Router.getInstance().currentRoute);
+    });
+
+    // Regression test for b/281728200.
+    test('Cellular network not found while not in detail subpage', async () => {
+      init();
+      mojoApi_.setNetworkTypeEnabledState(NetworkType.kCellular, true);
+
+      // Simulate navigating to top-level internet page.
+      let params = new URLSearchParams();
+      Router.getInstance().navigateTo(routes.INTERNET, params);
+      assertEquals(routes.INTERNET, Router.getInstance().currentRoute);
+
+      // Simulate navigating to mobile data subpage.
+      params = new URLSearchParams();
+      params.append(
+          'type', OncMojo.getNetworkTypeString(NetworkType.kCellular));
+      Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
+      assertEquals(routes.INTERNET_NETWORKS, Router.getInstance().currentRoute);
+      await flushAsync();
+
+      // Trigger |internetDetailPage| attempting to fetch the network. Because
+      // the page is not the current route, it should not trigger a navigation
+      // backwards.
+      internetDetailPage.init('cellular_guid', 'Cellular', 'cellular');
+      await flushAsync();
+
+      assertEquals(routes.INTERNET_NETWORKS, Router.getInstance().currentRoute);
+    });
   });
 
   suite('DetailsPageEthernet', function() {
diff --git a/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_accounts_test.js b/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_accounts_subpage_test.js
similarity index 98%
rename from chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_accounts_test.js
rename to chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_accounts_subpage_test.js
index 10a1caf..64fc4630 100644
--- a/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_accounts_test.js
+++ b/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_accounts_subpage_test.js
@@ -4,16 +4,19 @@
 
 'use strict';
 
-import {TestKerberosAccountsBrowserProxy, TEST_KERBEROS_ACCOUNTS} from './test_kerberos_accounts_browser_proxy.js';
-import {Router, Route, routes, KerberosErrorType, KerberosConfigErrorCode, KerberosAccountsBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
+import 'chrome://os-settings/chromeos/lazy_load.js';
+
+import {Router, Route, routes} from 'chrome://os-settings/chromeos/os_settings.js';
+import {KerberosAccountsBrowserProxyImpl, KerberosConfigErrorCode, KerberosErrorType} from 'chrome://os-settings/chromeos/lazy_load.js';
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {webUIListenerCallback} from 'chrome://resources/ash/common/cr.m.js';
+import {TestKerberosAccountsBrowserProxy, TEST_KERBEROS_ACCOUNTS} from './test_kerberos_accounts_browser_proxy.js';
 
-// Tests for the Kerberos Accounts settings page.
+// Tests for the Kerberos Accounts settings subpage.
 suite('KerberosAccountsTests', function() {
   let browserProxy = null;
   let kerberosAccounts = null;
@@ -57,7 +60,8 @@
       kerberosAccounts.remove();
     }
 
-    kerberosAccounts = document.createElement('settings-kerberos-accounts');
+    kerberosAccounts =
+        document.createElement('settings-kerberos-accounts-subpage');
     document.body.appendChild(kerberosAccounts);
 
     accountList = kerberosAccounts.shadowRoot.querySelector('#account-list');
diff --git a/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_page_test.ts b/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_page_test.ts
index 47ee5795..c3ffb09 100644
--- a/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/kerberos_page/kerberos_page_test.ts
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {KerberosAccountsBrowserProxyImpl, Route, Router, routes, SettingsKerberosPageElement} from 'chrome://os-settings/chromeos/os_settings.js';
+import 'chrome://os-settings/chromeos/os_settings.js';
+
+import {KerberosAccountsBrowserProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {Route, Router, routes, SettingsKerberosPageElement} from 'chrome://os-settings/chromeos/os_settings.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse} from 'chrome://webui-test/chai_assert.js';
@@ -37,7 +40,7 @@
 
     // Sub-page trigger is shown.
     const subpageTrigger = kerberosPage.shadowRoot!.querySelector<HTMLElement>(
-        '#kerberos-accounts-subpage-trigger');
+        '#kerberosAccountsSubpageTrigger');
     assert(subpageTrigger);
     assertFalse(subpageTrigger.hidden);
 
diff --git a/chrome/test/data/webui/settings/chromeos/kerberos_page/test_kerberos_accounts_browser_proxy.ts b/chrome/test/data/webui/settings/chromeos/kerberos_page/test_kerberos_accounts_browser_proxy.ts
index 0e6fc40..5accd80 100644
--- a/chrome/test/data/webui/settings/chromeos/kerberos_page/test_kerberos_accounts_browser_proxy.ts
+++ b/chrome/test/data/webui/settings/chromeos/kerberos_page/test_kerberos_accounts_browser_proxy.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {KerberosAccount, KerberosAccountsBrowserProxy, KerberosConfigErrorCode, KerberosErrorType, ValidateKerberosConfigResult} from 'chrome://os-settings/chromeos/os_settings.js';
+import {KerberosAccount, KerberosAccountsBrowserProxy, KerberosConfigErrorCode, KerberosErrorType, ValidateKerberosConfigResult} from 'chrome://os-settings/chromeos/lazy_load.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 // List of fake accounts.
diff --git a/chrome/test/data/webui/settings/chromeos/os_a11y_page/chromevox_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_a11y_page/chromevox_subpage_test.ts
index e3dbb2e..f1d6e9f 100644
--- a/chrome/test/data/webui/settings/chromeos/os_a11y_page/chromevox_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_a11y_page/chromevox_subpage_test.ts
@@ -8,7 +8,7 @@
 
 import 'chrome://os-settings/chromeos/lazy_load.js';
 
-import {BluetoothBrailleDisplayUiElement, SettingsChromeVoxSubpageElement} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {SettingsChromeVoxSubpageElement} from 'chrome://os-settings/chromeos/lazy_load.js';
 import {ChromeVoxSubpageBrowserProxyImpl, CrSettingsPrefs, SettingsDropdownMenuElement, SettingsPrefsElement} from 'chrome://os-settings/chromeos/os_settings.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -286,8 +286,7 @@
 
     // Get Bluetooth Braille Display UI element.
     const bluetoothBrailleDisplayUi =
-        page.shadowRoot!.querySelector<BluetoothBrailleDisplayUiElement>(
-            'bluetooth-braille-display-ui');
+        page.shadowRoot!.querySelector('bluetooth-braille-display-ui');
     assert(bluetoothBrailleDisplayUi);
 
     // Update list of devices.
@@ -300,8 +299,7 @@
     assert(dropdownMenu);
 
     // Get Bluetooth Braille Display select element.
-    const selectElement =
-        dropdownMenu.shadowRoot!.querySelector<HTMLSelectElement>('select');
+    const selectElement = dropdownMenu.shadowRoot!.querySelector('select');
     assert(selectElement);
 
     // Select VarioUltra simulated device.
@@ -313,4 +311,52 @@
     // Update list of devices.
     await bluetoothBrailleDisplayUi.onDisplayListChanged(displays);
   });
+
+  test('no custom dropdown item shown', async function() {
+    // Get Bluetooth Braille Display UI element.
+    const bluetoothBrailleDisplayUi =
+        page.shadowRoot!.querySelector('bluetooth-braille-display-ui');
+    assert(bluetoothBrailleDisplayUi);
+
+    // Get Bluetooth Braille Display dropdown element.
+    const dropdownMenu =
+        bluetoothBrailleDisplayUi.shadowRoot!
+            .querySelector<SettingsDropdownMenuElement>('#displaySelect');
+    assert(dropdownMenu);
+
+    // Get Bluetooth Braille Display select element.
+    const selectElement = dropdownMenu.shadowRoot!.querySelector('select');
+    assert(selectElement);
+
+    // Verify Bluetooth Braille Display select element is blank (not custom).
+    assertEquals('', selectElement.value);
+  });
+
+  test('braille display shown', async function() {
+    // Mock chrome.bluetooth.getDevice using `display` as the backing source.
+    const displays = [{name: 'VarioUltra', address: 'abcd1234', paired: true}];
+    chrome.bluetooth.getDevice = async address =>
+        displays.find(display => display.address === address)!;
+
+    // Get Bluetooth Braille Display UI element.
+    const bluetoothBrailleDisplayUi =
+        page.shadowRoot!.querySelector('bluetooth-braille-display-ui');
+    assert(bluetoothBrailleDisplayUi);
+
+    // Update list of devices.
+    await bluetoothBrailleDisplayUi.onDisplayListChanged(displays);
+
+    // Get Bluetooth Braille Display dropdown element.
+    const dropdownMenu =
+        bluetoothBrailleDisplayUi.shadowRoot!
+            .querySelector<SettingsDropdownMenuElement>('#displaySelect');
+    assert(dropdownMenu);
+
+    // Get Bluetooth Braille Display select element.
+    const selectElement = dropdownMenu.shadowRoot!.querySelector('select');
+    assert(selectElement);
+
+    // Verify VarioUltra Bluetooth Braille Display is selected.
+    assertEquals('abcd1234', selectElement.value);
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_a11y_page/text_to_speech_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_a11y_page/text_to_speech_subpage_test.ts
index 1403d87..231df842 100644
--- a/chrome/test/data/webui/settings/chromeos/os_a11y_page/text_to_speech_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_a11y_page/text_to_speech_subpage_test.ts
@@ -4,12 +4,13 @@
 
 import 'chrome://os-settings/chromeos/lazy_load.js';
 
-import {SettingsTextToSpeechSubpageElement} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {PdfOcrUserSelection, SettingsTextToSpeechSubpageElement} from 'chrome://os-settings/chromeos/lazy_load.js';
 import {CrSettingsPrefs, Router, routes, SettingsPrefsElement, SettingsToggleButtonElement} from 'chrome://os-settings/chromeos/os_settings.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {fakeMetricsPrivate} from 'chrome://webui-test/metrics_test_support.js';
 import {waitAfterNextRender, waitBeforeNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 
@@ -113,4 +114,61 @@
         assertTrue(pdfOcrToggle.checked);
         assertTrue(page.prefs.settings.a11y.pdf_ocr_always_active.value);
       });
+
+  test('pdf ocr toggle on invokes uma metric', async function() {
+    // `features::kPdfOcr` is enabled in os_settings_v3_browsertest.js
+    assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
+
+    const metrics = fakeMetricsPrivate();
+
+    await initPage();
+    // Simulate enabling the ChromeVox.
+    page.hasScreenReader = true;
+
+    const pdfOcrToggle =
+        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+            '#crosPdfOcrToggle');
+    assert(pdfOcrToggle);
+
+    // Turn on PDF OCR always.
+    pdfOcrToggle.click();
+    await waitAfterNextRender(pdfOcrToggle);
+    assertTrue(pdfOcrToggle.checked);
+    assertEquals(
+        1,
+        metrics.count(
+            'Accessibility.PdfOcr.UserSelection',
+            PdfOcrUserSelection.TURN_ON_ALWAYS_FROM_SETTINGS));
+  });
+
+  test('pdf ocr toggle off invokes uma metric', async function() {
+    // `features::kPdfOcr` is enabled in os_settings_v3_browsertest.js
+    assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
+
+    const metrics = fakeMetricsPrivate();
+
+    await initPage();
+    // Simulate enabling the ChromeVox.
+    page.hasScreenReader = true;
+
+    const pdfOcrToggle =
+        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+            '#crosPdfOcrToggle');
+    assert(pdfOcrToggle);
+
+    // Turn on PDF OCR always.
+    pdfOcrToggle.click();
+    await waitAfterNextRender(pdfOcrToggle);
+    assertTrue(pdfOcrToggle.checked);
+
+    // Turn off PDF OCR.
+    pdfOcrToggle.click();
+    await waitAfterNextRender(pdfOcrToggle);
+    assertFalse(pdfOcrToggle.checked);
+    assertEquals(
+        1,
+        metrics.count(
+            'Accessibility.PdfOcr.UserSelection',
+            PdfOcrUserSelection.TURN_OFF_FROM_SETTINGS));
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page/fingerprint_list_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_people_page/fingerprint_list_subpage_test.ts
new file mode 100644
index 0000000..a796696
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page/fingerprint_list_subpage_test.ts
@@ -0,0 +1,365 @@
+// 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.
+
+import 'chrome://os-settings/chromeos/lazy_load.js';
+
+import {FingerprintBrowserProxyImpl, FingerprintResultType, FingerprintSetupStep, SettingsFingerprintListSubpageElement, SettingsSetupFingerprintDialogElement} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
+import {DomRepeatEvent, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+import {TestFingerprintBrowserProxy} from './test_fingerprint_browser_proxy.js';
+
+suite('<settings-fingerprint-list-subpage>', () => {
+  let fingerprintList: SettingsFingerprintListSubpageElement;
+  let dialog: SettingsSetupFingerprintDialogElement;
+  let addAnotherButton: HTMLButtonElement;
+  let browserProxy: TestFingerprintBrowserProxy;
+
+  function createFakeEvent(index: number, label?: string) {
+    return {model: {index: index, item: label || ''}} as DomRepeatEvent<string>;
+  }
+
+  function openDialog() {
+    const actionButton =
+        fingerprintList.shadowRoot!.querySelector<HTMLButtonElement>(
+            '.action-button');
+    assertTrue(!!actionButton);
+    actionButton.click();
+    flush();
+    const dialogElement = fingerprintList.shadowRoot!.querySelector(
+        'settings-setup-fingerprint-dialog');
+    assertTrue(!!dialogElement);
+    dialog = dialogElement;
+    const button = dialog.shadowRoot!.querySelector<HTMLButtonElement>(
+        '#addAnotherButton');
+    assertTrue(!!button);
+    addAnotherButton = button;
+  }
+
+  setup(async () => {
+    browserProxy = new TestFingerprintBrowserProxy();
+    FingerprintBrowserProxyImpl.setInstanceForTesting(browserProxy);
+
+    fingerprintList =
+        document.createElement('settings-fingerprint-list-subpage');
+    document.body.appendChild(fingerprintList);
+    flush();
+    await browserProxy.whenCalled('getFingerprintsList');
+    assertEquals(0, fingerprintList.get('fingerprints_').length);
+    browserProxy.resetResolver('getFingerprintsList');
+  });
+
+  test('EnrollingFingerprintLottieAnimation', async () => {
+    loadTimeData.overrideValues({fingerprintUnlockEnabled: true});
+    openDialog();
+    await browserProxy.whenCalled('startEnroll');
+    const dialogButton =
+        dialog.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+    assertTrue(!!dialogButton);
+    assertTrue(dialogButton.open);
+    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.get('step_'));
+    const element =
+        dialog.shadowRoot!.querySelector<HTMLElement>('#scannerLocationLottie');
+    assertTrue(!!element);
+    assertFalse(element.hidden);
+  });
+
+  // Verify running through the enroll session workflow
+  // (settings-setup-fingerprint-dialog) works as expected.
+  test('EnrollingFingerprint', async () => {
+    loadTimeData.overrideValues({fingerprintUnlockEnabled: true});
+    openDialog();
+    await browserProxy.whenCalled('startEnroll');
+    const dialogButton =
+        dialog.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+    assertTrue(!!dialogButton);
+    assertTrue(dialogButton.open);
+    assertEquals(0, dialog.get('percentComplete_'));
+    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.get('step_'));
+    const element =
+        dialog.shadowRoot!.querySelector<HTMLElement>('#scannerLocationLottie');
+    assertTrue(!!element);
+    assertFalse(element.hidden);
+    const arcElement = dialog.shadowRoot!.querySelector<HTMLElement>('#arc');
+    assertTrue(!!arcElement);
+    assertTrue(arcElement.hidden);
+    // Message should be shown for LOCATE_SCANNER step.
+    const message = dialog.shadowRoot!.querySelector('#messageDiv');
+    assertTrue(!!message);
+    assertEquals('visible', window.getComputedStyle(message).visibility);
+
+    // First tap on the sensor to start fingerprint enrollment.
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 20 /* percent */);
+    assertEquals(20, dialog.get('percentComplete_'));
+    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.get('step_'));
+    assertTrue(element.hidden);
+    assertFalse(arcElement.hidden);
+
+    // Verify that by sending a scan problem, the div that contains the
+    // problem message should be visible.
+    browserProxy.scanReceived(
+        FingerprintResultType.TOO_FAST, false, 20 /* percent */);
+    assertEquals(20, dialog.get('percentComplete_'));
+    assertEquals('visible', window.getComputedStyle(message).visibility);
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 50 /* percent */);
+    assertEquals('hidden', window.getComputedStyle(message).visibility);
+    assertEquals(50, dialog.get('percentComplete_'));
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 70 /* percent */);
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, true, 100 /* percent */);
+    assertEquals(FingerprintSetupStep.READY, dialog.get('step_'));
+    // Message should be shown for READY step.
+    assertEquals('visible', window.getComputedStyle(message).visibility);
+
+    // Verify that by tapping the continue button we should exit the dialog
+    // and the fingerprint list should have one fingerprint registered.
+    const closeButton =
+        dialog.shadowRoot!.querySelector<HTMLButtonElement>('#closeButton');
+    assertTrue(!!closeButton);
+    closeButton.click();
+    await flushTasks();
+    await browserProxy.whenCalled('getFingerprintsList');
+    assertEquals(1, fingerprintList.get('fingerprints_').length);
+  });
+
+  // Verify enrolling a fingerprint, then enrolling another without closing the
+  // dialog works as intended.
+  test('EnrollingAnotherFingerprint', async () => {
+    loadTimeData.overrideValues({fingerprintUnlockEnabled: true});
+    openDialog();
+    await browserProxy.whenCalled('startEnroll');
+    browserProxy.resetResolver('startEnroll');
+
+    const dialogButton =
+        dialog.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+    assertTrue(!!dialogButton);
+    assertTrue(dialogButton.open);
+    assertEquals(0, dialog.get('percentComplete_'));
+    assertFalse(isVisible(addAnotherButton));
+    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.get('step_'));
+
+    // First tap on the sensor to start fingerprint enrollment.
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 20 /* percent */);
+    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.get('step_'));
+
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, true, 100 /* percent */);
+    assertEquals(FingerprintSetupStep.READY, dialog.get('step_'));
+
+    assertTrue(dialogButton.open);
+    assertTrue(isVisible(addAnotherButton));
+    addAnotherButton.click();
+
+    // Once the first fingerprint is enrolled, verify that enrolling the
+    // second fingerprint without closing the dialog works as expected.
+    await Promise.all([
+      browserProxy.whenCalled('startEnroll'),
+      browserProxy.whenCalled('getFingerprintsList'),
+    ]);
+    browserProxy.resetResolver('getFingerprintsList');
+
+    assertTrue(dialogButton.open);
+    assertFalse(isVisible(addAnotherButton));
+    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.get('step_'));
+    const element =
+        dialog.shadowRoot!.querySelector<HTMLElement>('#scannerLocationLottie');
+    assertTrue(!!element);
+    assertTrue(element.hidden);
+    const arcElement = dialog.shadowRoot!.querySelector<HTMLElement>('#arc');
+    assertTrue(!!arcElement);
+    assertFalse(arcElement.hidden);
+
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, true, 100 /* percent */);
+
+    // Verify that by tapping the continue button we should exit the
+    // dialog and the fingerprint list should have two fingerprints
+    // registered.
+    const closeButton =
+        dialog.shadowRoot!.querySelector<HTMLButtonElement>('#closeButton');
+    assertTrue(!!closeButton);
+    closeButton.click();
+    await browserProxy.whenCalled('getFingerprintsList');
+    assertEquals(2, fingerprintList.get('fingerprints_').length);
+  });
+
+  // Verify after third fingerprint is enrolled, add another button in the
+  // setup dialog is hidden.
+  test('EnrollingThirdFingerprint', async () => {
+    browserProxy.setFingerprints(['1', '2']);
+    fingerprintList['updateFingerprintsList_']();
+
+    openDialog();
+    await browserProxy.whenCalled('startEnroll');
+    browserProxy.resetResolver('startEnroll');
+
+    const dialogButton =
+        dialog.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+    assertTrue(!!dialogButton);
+    assertTrue(dialogButton.open);
+    assertEquals(0, dialog.get('percentComplete_'));
+    assertFalse(isVisible(addAnotherButton));
+    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.get('step_'));
+
+    // First tap on the sensor to start fingerprint enrollment.
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 20 /* percent */);
+    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.get('step_'));
+
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, true, 100 /* percent */);
+    assertEquals(FingerprintSetupStep.READY, dialog.get('step_'));
+    await browserProxy.whenCalled('getFingerprintsList');
+    browserProxy.resetResolver('getFingerprintsList');
+
+    // Add another is hidden after third fingerprint is enrolled.
+    assertTrue(dialogButton.open);
+    assertFalse(isVisible(addAnotherButton));
+    assertEquals(3, fingerprintList.get('fingerprints_').length);
+  });
+
+  test('CancelEnrollingFingerprint', async () => {
+    openDialog();
+    await browserProxy.whenCalled('startEnroll');
+    const dialogButton =
+        dialog.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+    assertTrue(!!dialogButton);
+    assertTrue(dialogButton.open);
+    assertEquals(0, dialog.get('percentComplete_'));
+    assertEquals(FingerprintSetupStep.LOCATE_SCANNER, dialog.get('step_'));
+    // First tap on the sensor to start fingerprint enrollment.
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 20 /* percent */);
+    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.get('step_'));
+
+    browserProxy.scanReceived(
+        FingerprintResultType.SUCCESS, false, 30 /* percent */);
+    assertEquals(30, dialog.get('percentComplete_'));
+    assertEquals(FingerprintSetupStep.MOVE_FINGER, dialog.get('step_'));
+
+    // Verify that by tapping the exit button we should exit the dialog
+    // and the fingerprint list should have zero fingerprints registered.
+    const closeButton =
+        dialog.shadowRoot!.querySelector<HTMLButtonElement>('#closeButton');
+    assertTrue(!!closeButton);
+    closeButton.click();
+    await browserProxy.whenCalled('cancelCurrentEnroll');
+    assertEquals(0, fingerprintList.get('fingerprints_').length);
+  });
+
+  test('RemoveFingerprint', async () => {
+    browserProxy.setFingerprints(['Label 1', 'Label 2']);
+    fingerprintList['updateFingerprintsList_']();
+
+    await browserProxy.whenCalled('getFingerprintsList');
+    browserProxy.resetResolver('getFingerprintsList');
+    assertEquals(2, fingerprintList.get('fingerprints_').length);
+    fingerprintList['onFingerprintDeleteTapped_'](createFakeEvent(0));
+
+    await Promise.all([
+      browserProxy.whenCalled('removeEnrollment'),
+      browserProxy.whenCalled('getFingerprintsList'),
+    ]);
+    assertEquals(1, fingerprintList.get('fingerprints_').length);
+  });
+
+  test('Deep link to add fingerprint', async () => {
+    const settingId = '1111';
+
+    browserProxy.setFingerprints(['Label 1', 'Label 2']);
+    fingerprintList['updateFingerprintsList_']();
+    await browserProxy.whenCalled('getFingerprintsList');
+
+    const params = new URLSearchParams();
+    params.append('settingId', settingId);
+    Router.getInstance().navigateTo(routes.FINGERPRINT, params);
+
+    flush();
+
+    const deepLinkElement =
+        fingerprintList.shadowRoot!.querySelector<HTMLElement>(
+            '#addFingerprint');
+    assertTrue(!!deepLinkElement);
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        'Add button should be focused for settingId=' + settingId);
+  });
+
+  test('Deep link to remove fingerprint', async () => {
+    const settingId = '1112';
+
+    browserProxy.setFingerprints(['Label 1', 'Label 2']);
+    fingerprintList['updateFingerprintsList_']();
+    await browserProxy.whenCalled('getFingerprintsList');
+
+    const params = new URLSearchParams();
+    params.append('settingId', settingId);
+    Router.getInstance().navigateTo(routes.FINGERPRINT, params);
+
+    flush();
+
+    const deepLinkElement =
+        fingerprintList.root!.querySelectorAll('cr-icon-button')[0];
+    assertTrue(!!deepLinkElement);
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        'Trash can button should be focused for settingId=' + settingId);
+  });
+
+  test('ChangeFingerprintLabel', async () => {
+    browserProxy.setFingerprints(['Label 1']);
+    fingerprintList['updateFingerprintsList_']();
+
+    await browserProxy.whenCalled('getFingerprintsList');
+    assertEquals(1, fingerprintList.get('fingerprints_').length);
+    assertEquals('Label 1', fingerprintList.get('fingerprints_')[0]);
+
+    // Verify that by sending a fingerprint input change event, the new
+    // label gets changed as expected.
+    fingerprintList['onFingerprintLabelChanged_'](
+        createFakeEvent(0, 'New Label 1'));
+
+    await Promise.all([
+      browserProxy.whenCalled('changeEnrollmentLabel'),
+      browserProxy.whenCalled('getFingerprintsList'),
+    ]);
+    assertEquals('New Label 1', fingerprintList.get('fingerprints_')[0]);
+  });
+
+  test('AddingNewFingerprint', async () => {
+    browserProxy.setFingerprints(['1', '2', '3']);
+    fingerprintList['updateFingerprintsList_']();
+
+    // Verify that new fingerprints cannot be added when there are already three
+    // registered fingerprints.
+    await browserProxy.whenCalled('getFingerprintsList');
+    browserProxy.resetResolver('getFingerprintsList');
+    assertEquals(3, fingerprintList.get('fingerprints_').length);
+    const actionButton =
+        fingerprintList.shadowRoot!.querySelector<HTMLButtonElement>(
+            '.action-button');
+    assertTrue(!!actionButton);
+    assertTrue(actionButton.disabled);
+    fingerprintList['onFingerprintDeleteTapped_'](createFakeEvent(0));
+
+    await Promise.all([
+      browserProxy.whenCalled('removeEnrollment'),
+      browserProxy.whenCalled('getFingerprintsList'),
+    ]);
+    assertEquals(2, fingerprintList.get('fingerprints_').length);
+    assertFalse(actionButton.disabled);
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page/test_fingerprint_browser_proxy.ts b/chrome/test/data/webui/settings/chromeos/os_people_page/test_fingerprint_browser_proxy.ts
new file mode 100644
index 0000000..d6a0f6c
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page/test_fingerprint_browser_proxy.ts
@@ -0,0 +1,83 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {FingerprintBrowserProxy, FingerprintInfo, FingerprintResultType} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+export class TestFingerprintBrowserProxy extends TestBrowserProxy implements
+    FingerprintBrowserProxy {
+  private fingerprintsList_: string[];
+  constructor() {
+    super([
+      'getFingerprintsList',
+      'getNumFingerprints',
+      'startEnroll',
+      'cancelCurrentEnroll',
+      'getEnrollmentLabel',
+      'removeEnrollment',
+      'changeEnrollmentLabel',
+      'fakeScanComplete',
+    ]);
+    this.fingerprintsList_ = [];
+  }
+
+  setFingerprints(fingerprints: string[]): void {
+    this.fingerprintsList_ = fingerprints.slice();
+  }
+
+  scanReceived(
+      result: FingerprintResultType, complete: boolean, percent: number): void {
+    if (complete) {
+      this.fingerprintsList_.push('New Label');
+    }
+
+    webUIListenerCallback(
+        'on-fingerprint-scan-received',
+        {result: result, isComplete: complete, percentComplete: percent});
+  }
+
+  getFingerprintsList(): Promise<FingerprintInfo> {
+    this.methodCalled('getFingerprintsList');
+    const fingerprintInfo: FingerprintInfo = {
+      fingerprintsList: this.fingerprintsList_.slice(),
+      isMaxed: this.fingerprintsList_.length >= 3,
+    };
+    return Promise.resolve(fingerprintInfo);
+  }
+
+  getNumFingerprints(): Promise<number> {
+    this.methodCalled('getNumFingerprints');
+    return Promise.resolve(this.fingerprintsList_.length);
+  }
+
+  startEnroll(): void {
+    this.methodCalled('startEnroll');
+  }
+
+  cancelCurrentEnroll(): void {
+    this.methodCalled('cancelCurrentEnroll');
+  }
+
+  getEnrollmentLabel(index: number): Promise<string> {
+    this.methodCalled('getEnrollmentLabel');
+    return Promise.resolve(this.fingerprintsList_[index] as string);
+  }
+
+  removeEnrollment(index: number): Promise<boolean> {
+    this.fingerprintsList_.splice(index, 1);
+    this.methodCalled('removeEnrollment', index);
+    return Promise.resolve(true);
+  }
+
+  changeEnrollmentLabel(index: number, newLabel: string): Promise<boolean> {
+    this.fingerprintsList_[index] = newLabel;
+    this.methodCalled('changeEnrollmentLabel', index, newLabel);
+    return Promise.resolve(true);
+  }
+
+  fakeScanComplete(): void {
+    chrome.send('fakeScanComplete');
+  }
+}
diff --git a/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_subpage_test.ts
index d2da628..93d583f 100644
--- a/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_subpage_test.ts
@@ -5,7 +5,7 @@
 import 'chrome://os-settings/chromeos/lazy_load.js';
 
 import {MediaDevicesProxy, PrivacyHubBrowserProxyImpl, SettingsPrivacyHubSubpage} from 'chrome://os-settings/chromeos/lazy_load.js';
-import {MetricsConsentBrowserProxyImpl, OsSettingsPrivacyPageElement, Router, routes, SecureDnsMode, SettingsToggleButtonElement} from 'chrome://os-settings/chromeos/os_settings.js';
+import {MetricsConsentBrowserProxyImpl, OsSettingsPrivacyPageElement, Router, routes, SecureDnsMode, settingMojom, SettingsToggleButtonElement} from 'chrome://os-settings/chromeos/os_settings.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -223,6 +223,25 @@
     }
   });
 
+  test('Deep link to speak-on-mute toggle on privacy hub', async () => {
+    const params = new URLSearchParams();
+    const settingId = settingMojom.Setting.kSpeakOnMuteDetectionOnOff;
+    params.append('settingId', `${settingId}`);
+    Router.getInstance().navigateTo(routes.PRIVACY_HUB, params);
+
+    flush();
+
+    const deepLinkElement = privacyHubSubpage.shadowRoot!
+                                .querySelector('#speakonmuteDetectionToggle')!
+                                .shadowRoot!.querySelector('cr-toggle');
+    assert(deepLinkElement);
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        `Speak-on-mute detection toggle should be focused for settingId=${
+            settingId}.`);
+  });
+
   test('Camera, microphone toggle, their sublabel and their list', async () => {
     const getNoCameraText = () =>
         privacyHubSubpage.shadowRoot!.querySelector('#noCamera');
diff --git a/chrome/test/data/webui/settings/chromeos/os_search_page/google_assistant_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_search_page/google_assistant_subpage_test.ts
new file mode 100644
index 0000000..26d5935
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_search_page/google_assistant_subpage_test.ts
@@ -0,0 +1,506 @@
+// 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.
+
+import 'chrome://os-settings/chromeos/lazy_load.js';
+
+import {ConsentStatus, DspHotwordState, GoogleAssistantBrowserProxy, GoogleAssistantBrowserProxyImpl, SettingsGoogleAssistantSubpageElement} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {ControlledButtonElement, CrSettingsPrefs, Router, routes, SettingsPrefsElement, SettingsToggleButtonElement} from 'chrome://os-settings/chromeos/os_settings.js';
+import {CrLinkRowElement} from 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {TestMock} from 'chrome://webui-test/test_mock.js';
+
+suite('<settings-google-assistant-subpage>', () => {
+  let page: SettingsGoogleAssistantSubpageElement;
+  let browserProxy: TestMock<GoogleAssistantBrowserProxy>&
+      GoogleAssistantBrowserProxy;
+  let prefElement: SettingsPrefsElement;
+
+  suiteSetup(() => {
+    loadTimeData.overrideValues({
+      isAssistantAllowed: true,
+      hotwordDspAvailable: true,
+    });
+  });
+
+  setup(async () => {
+    browserProxy = TestMock.fromClass(GoogleAssistantBrowserProxyImpl);
+    GoogleAssistantBrowserProxyImpl.setInstanceForTesting(browserProxy);
+
+    prefElement = document.createElement('settings-prefs');
+    document.body.appendChild(prefElement);
+
+    await CrSettingsPrefs.initialized;
+    page = document.createElement('settings-google-assistant-subpage');
+    page.prefs = prefElement.prefs;
+    document.body.appendChild(page);
+  });
+
+  teardown(() => {
+    page.remove();
+    prefElement.remove();
+    CrSettingsPrefs.resetForTesting();
+  });
+
+  test('toggleAssistant', () => {
+    flush();
+    const button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-enable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    // Tap the enable toggle button and ensure the state becomes enabled.
+    button.click();
+    flush();
+    assertTrue(button.checked);
+  });
+
+  test('toggleAssistantContext', () => {
+    let button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-context-enable');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.context.enabled', false);
+    flush();
+    button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-context-enable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    flush();
+    assertTrue(button.checked);
+    assertTrue(
+        !!page.getPref('settings.voice_interaction.context.enabled.value'));
+  });
+
+  test('toggleAssistantHotword', async () => {
+    let button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-hotword-enable');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
+    flush();
+    button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-hotword-enable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    flush();
+    assertTrue(button.checked);
+    assertTrue(
+        !!page.getPref('settings.voice_interaction.hotword.enabled.value'));
+    await browserProxy.whenCalled('syncVoiceModelStatus');
+  });
+
+  test('hotwordToggleVisibility', () => {
+    let button =
+        page.shadowRoot!.querySelector('#google-assistant-hotword-enable');
+    assertEquals(null, button);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    button = page.shadowRoot!.querySelector('#google-assistant-hotword-enable');
+    assertTrue(!!button);
+  });
+
+  test('hotwordToggleDisabledForChildUser', () => {
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.set('prefs.settings.voice_interaction.hotword.enabled', {
+      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
+      controlledBy: chrome.settingsPrivate.ControlledBy.CHILD_RESTRICTION,
+      value: false,
+    });
+
+    flush();
+    const button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-hotword-enable');
+    assertTrue(!!button);
+    const indicator =
+        button.shadowRoot!.querySelector('cr-policy-pref-indicator');
+    assertTrue(!!indicator);
+    assertTrue(button.disabled);
+  });
+
+  test('tapOnRetrainVoiceModel', async () => {
+    let button = page.shadowRoot!.querySelector<ControlledButtonElement>(
+        '#retrain-voice-model');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue(
+        'settings.voice_interaction.activity_control.consent_status',
+        ConsentStatus.ACTIVITY_CONTROL_ACCEPTED);
+    flush();
+    button = page.shadowRoot!.querySelector<ControlledButtonElement>(
+        '#retrain-voice-model');
+    assertTrue(!!button);
+
+    button.click();
+    flush();
+    await browserProxy.whenCalled('retrainAssistantVoiceModel');
+  });
+
+  test('retrainButtonVisibility', () => {
+    let button = page.shadowRoot!.querySelector('#retrain-voice-model');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+    button = page.shadowRoot!.querySelector('#retrain-voice-model');
+    assertEquals(null, button);
+
+    // Hotword disabled.
+    // Button should not be shown.
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
+    flush();
+    button = page.shadowRoot!.querySelector('#retrain-voice-model');
+    assertEquals(null, button);
+
+    // Hotword enabled.
+    // Button should be shown.
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    flush();
+    button = page.shadowRoot!.querySelector('#retrain-voice-model');
+    assertTrue(!!button);
+  });
+
+  test('Deep link to retrain voice model', async () => {
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue(
+        'settings.voice_interaction.activity_control.consent_status',
+        ConsentStatus.ACTIVITY_CONTROL_ACCEPTED);
+    flush();
+
+    const params = new URLSearchParams();
+    params.append('settingId', '607');
+    Router.getInstance().navigateTo(routes.GOOGLE_ASSISTANT, params);
+
+    const button = page.shadowRoot!.querySelector<ControlledButtonElement>(
+        '#retrain-voice-model');
+    assertTrue(!!button);
+    const deepLinkElement = button.shadowRoot!.querySelector('cr-button');
+    assertTrue(!!deepLinkElement);
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        'Retrain model button should be focused for settingId=607.');
+  });
+
+  test('toggleAssistantNotification', () => {
+    let button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-notification-enable');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.notification.enabled', false);
+    flush();
+    button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-notification-enable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    flush();
+    assertTrue(button.checked);
+    assertTrue(!!page.getPref(
+        'settings.voice_interaction.notification.enabled.value'));
+  });
+
+  test('toggleAssistantLaunchWithMicOpen', () => {
+    let button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-launch-with-mic-open');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.launch_with_mic_open', false);
+    flush();
+    button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-launch-with-mic-open');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    flush();
+    assertTrue(button.checked);
+    assertTrue(!!page.getPref(
+        'settings.voice_interaction.launch_with_mic_open.value'));
+  });
+
+  test('tapOnAssistantSettings', async () => {
+    let button = page.shadowRoot!.querySelector<CrLinkRowElement>(
+        '#google-assistant-settings');
+    assertEquals(null, button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+    button = page.shadowRoot!.querySelector<CrLinkRowElement>(
+        '#google-assistant-settings');
+    assertTrue(!!button);
+
+    button.click();
+    flush();
+    await browserProxy.whenCalled('showGoogleAssistantSettings');
+  });
+
+  test('assistantDisabledByPolicy', () => {
+    let button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-enable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+    button = page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+        '#google-assistant-enable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertTrue(button.checked);
+
+    page.setPrefValue('settings.assistant.disabled_by_policy', true);
+    flush();
+    assertTrue(!!button);
+    assertTrue(button.disabled);
+    assertFalse(button.checked);
+  });
+});
+
+suite('<settings-google-assistant-subpage> With No Dsp Hotword', () => {
+  let page: SettingsGoogleAssistantSubpageElement;
+  let browserProxy: TestMock<GoogleAssistantBrowserProxy>&
+      GoogleAssistantBrowserProxy;
+  let prefElement: SettingsPrefsElement;
+
+  suiteSetup(() => {
+    loadTimeData.overrideValues({
+      isAssistantAllowed: true,
+      hotwordDspAvailable: false,
+    });
+  });
+
+  setup(async () => {
+    browserProxy = TestMock.fromClass(GoogleAssistantBrowserProxyImpl);
+    GoogleAssistantBrowserProxyImpl.setInstanceForTesting(browserProxy);
+
+    prefElement = document.createElement('settings-prefs');
+    document.body.appendChild(prefElement);
+
+    await CrSettingsPrefs.initialized;
+    page = document.createElement('settings-google-assistant-subpage');
+    page.prefs = prefElement.prefs;
+    document.body.appendChild(page);
+    flush();
+  });
+
+  teardown(() => {
+    page.remove();
+    prefElement.remove();
+    CrSettingsPrefs.resetForTesting();
+  });
+
+  function selectValue(select: HTMLSelectElement, value: string) {
+    select.value = value;
+    select.dispatchEvent(new CustomEvent('change'));
+    flush();
+  }
+
+  test('hotwordToggleVisibilityWithNoDspHotword', () => {
+    let toggle =
+        page.shadowRoot!.querySelector('#google-assistant-hotword-enable');
+    assertEquals(null, toggle);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    toggle = page.shadowRoot!.querySelector('#google-assistant-hotword-enable');
+    assertEquals(null, toggle);
+  });
+
+  test('dspHotwordDropdownVisibilityWithNoDspHotword', () => {
+    let container = page.shadowRoot!.querySelector('#dsp-hotword-container');
+    assertEquals(null, container);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    container = page.shadowRoot!.querySelector('#dsp-hotword-container');
+    assertTrue(!!container);
+  });
+
+  test('dspHotwordDropdownIndicatorEnabled', () => {
+    let indicator =
+        page.shadowRoot!.querySelector('#hotword-policy-pref-indicator');
+    assertEquals(null, indicator);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.set('prefs.settings.voice_interaction.hotword.enabled', {
+      enforcement: chrome.settingsPrivate.Enforcement.RECOMMENDED,
+      value: true,
+    });
+
+    flush();
+    const dropdown = page.shadowRoot!.querySelector('#dsp-hotword-state');
+    indicator =
+        page.shadowRoot!.querySelector('#hotword-policy-pref-indicator');
+    assertTrue(!!dropdown);
+    assertEquals(null, indicator);
+    assertFalse(dropdown.hasAttribute('disabled'));
+  });
+
+  test('dspHotwordDropdownIndicatorDisabled', () => {
+    let indicator =
+        page.shadowRoot!.querySelector('#hotword-policy-pref-indicator');
+    assertEquals(null, indicator);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.set('prefs.settings.voice_interaction.hotword.enabled', {
+      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
+      value: true,
+    });
+
+    flush();
+    const dropdown = page.shadowRoot!.querySelector('#dsp-hotword-state');
+    indicator =
+        page.shadowRoot!.querySelector('#hotword-policy-pref-indicator');
+    assertTrue(!!dropdown);
+    assertTrue(!!indicator);
+    assertTrue(dropdown.hasAttribute('disabled'));
+  });
+
+  test('dspHotwordDropdownDisabledForChildUser', () => {
+    let indicator =
+        page.shadowRoot!.querySelector('#hotword-policy-pref-indicator');
+    assertEquals(null, indicator);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.set('prefs.settings.voice_interaction.hotword.enabled', {
+      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
+      controlledBy: chrome.settingsPrivate.ControlledBy.CHILD_RESTRICTION,
+      value: false,
+    });
+
+    flush();
+    const dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    indicator =
+        page.shadowRoot!.querySelector('#hotword-policy-pref-indicator');
+    assertTrue(!!dropdown);
+    assertTrue(!!indicator);
+    assertTrue(dropdown.disabled);
+  });
+
+  test('dspHotwordDropdownSelection', () => {
+    let dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertEquals(null, dropdown);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertTrue(!!dropdown);
+    assertFalse(dropdown.disabled);
+
+    selectValue(dropdown, String(DspHotwordState.DEFAULT_ON));
+    flush();
+    assertTrue(
+        !!page.getPref('settings.voice_interaction.hotword.enabled.value'));
+    assertEquals(
+        false,
+        page.getPref('settings.voice_interaction.hotword.always_on.value'));
+
+    selectValue(dropdown, String(DspHotwordState.ALWAYS_ON));
+    flush();
+    assertTrue(
+        !!page.getPref('settings.voice_interaction.hotword.enabled.value'));
+    assertTrue(
+        !!page.getPref('settings.voice_interaction.hotword.always_on.value'));
+
+    selectValue(dropdown, String(DspHotwordState.OFF));
+    flush();
+    assertEquals(
+        false,
+        page.getPref('settings.voice_interaction.hotword.enabled.value'));
+    assertEquals(
+        false,
+        page.getPref('settings.voice_interaction.hotword.always_on.value'));
+  });
+
+  test('dspHotwordDropdownStatus', () => {
+    let dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertEquals(null, dropdown);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertTrue(!!dropdown);
+    assertFalse(dropdown.disabled);
+
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.always_on', false);
+    flush();
+    assertEquals(DspHotwordState.DEFAULT_ON, Number(dropdown.value));
+
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.always_on', true);
+    flush();
+    assertEquals(DspHotwordState.ALWAYS_ON, Number(dropdown.value));
+
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
+    page.setPrefValue('settings.voice_interaction.hotword.always_on', false);
+    flush();
+    assertEquals(DspHotwordState.OFF, Number(dropdown.value));
+  });
+
+  test('dspHotwordDropdownDefaultOnSync', async () => {
+    let dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertEquals(null, dropdown);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertTrue(!!dropdown);
+    assertFalse(dropdown.disabled);
+    selectValue(dropdown, String(DspHotwordState.OFF));
+    flush();
+
+    selectValue(dropdown, String(DspHotwordState.DEFAULT_ON));
+    flush();
+    await browserProxy.whenCalled('syncVoiceModelStatus');
+  });
+
+  test('dspHotwordDropdownAlwaysOnSync', async () => {
+    let dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertEquals(null, dropdown);
+
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    flush();
+
+    dropdown =
+        page.shadowRoot!.querySelector<HTMLSelectElement>('#dsp-hotword-state');
+    assertTrue(!!dropdown);
+    assertFalse(dropdown.disabled);
+    selectValue(dropdown, String(DspHotwordState.OFF));
+    flush();
+
+    selectValue(dropdown, String(DspHotwordState.ALWAYS_ON));
+    flush();
+    await browserProxy.whenCalled('syncVoiceModelStatus');
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 6853335..c3b9560 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -315,8 +315,6 @@
  ['EsimRemoveProfileDialog', 'esim_remove_profile_dialog_test.js'],
  ['EsimRenameDialog', 'esim_rename_dialog_test.js'],
  ['FakeCrosAudioConfig', 'fake_cros_audio_config_test.js'],
- ['FingerprintListSubpage', 'fingerprint_list_subpage_test.js'],
- ['GoogleAssistantSubpage', 'google_assistant_subpage_test.js'],
  ['GuestOsSharedPaths', 'guest_os/guest_os_shared_paths_test.js'],
  ['GuestOsSharedUsbDevices', 'guest_os/guest_os_shared_usb_devices_test.js'],
  [
@@ -387,7 +385,10 @@
  ['InternetSubpage', 'internet_subpage_tests.js'],
  ['InternetSubpageMenu', 'internet_subpage_menu_test.js'],
  ['KerberosPage', 'kerberos_page/kerberos_page_test.js'],
- ['KerberosPageKerberosAccounts', 'kerberos_page/kerberos_accounts_test.js'],
+ [
+   'KerberosPageKerberosAccountsSubpage',
+   'kerberos_page/kerberos_accounts_subpage_test.js',
+ ],
  [
    'KeyboardShortcutBanner',
    'keyboard_shortcut_banner/keyboard_shortcut_banner_test.js'
@@ -550,6 +551,10 @@
  ['OsPairedBluetoothListItem', 'os_paired_bluetooth_list_item_tests.js'],
  ['OsPageAvailability', 'os_page_availability_test.js'],
  ['OsPeoplePageAddUserDialog', 'os_people_page/add_user_dialog_test.js'],
+ [
+   'OsPeoplePageFingerprintListSubpage',
+   'os_people_page/fingerprint_list_subpage_test.js'
+ ],
  ['OsPrintingPage', 'os_printing_page/os_printing_page_test.js'],
  ['OsPrivacyPage', 'os_privacy_page/os_privacy_page_test.js'],
  [
@@ -562,6 +567,10 @@
    'os_privacy_page/smart_privacy_subpage_test.js'
  ],
  ['OsSearchPage', 'os_search_page/os_search_page_test.js'],
+ [
+   'OsSearchPageGoogleAssistantSubpage',
+   'os_search_page/google_assistant_subpage_test.js'
+ ],
  ['OsSearchPageSearchSubpage', 'os_search_page/search_subpage_test.js'],
  ['OsSettingsHatsUi', 'os_settings_ui/os_settings_hats_ui_test.js'],
  ['OsSettingsMenu', 'os_settings_menu/os_settings_menu_test.js'],
@@ -636,7 +645,8 @@
   // AboutPage has a test suite that can only succeed on official builds where
   // the is_chrome_branded build flag is enabled.
   if (testName === 'AboutPage') {
-    TEST_F(className, 'AllBuilds' || 'All', () => {
+    // TODO(b/282120688): Fix this test since it fails on "chrome/ci/linux-chromeos-chrome".
+    TEST_F(className, 'DISABLED_AllBuilds' || 'DISABLED_All', () => {
       mocha.grep('/^(?!AboutPageTest_OfficialBuild).*$/').run();
     });
 
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js b/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
index e43152c..58e22b8e 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
@@ -44,6 +44,10 @@
 
   /** @return {OsSettingsPageElement} */
   function init() {
+    loadTimeData.overrideValues({
+      isKerberosEnabled: true,  // Simulate Kerberos enabled
+    });
+
     const element = document.createElement('os-settings-page');
     element.prefs = prefElement.prefs;
     element.pageAvailability = createPageAvailabilityForTesting();
@@ -56,9 +60,6 @@
     suiteSetup(() => {
       Router.getInstance().navigateTo(routes.BASIC);
       settingsPage = init();
-
-      // For Kerberos page
-      settingsPage.showKerberosSection = true;
     });
 
     suiteTeardown(() => {
@@ -125,9 +126,6 @@
       loadTimeData.overrideValues({isGuest: true});
       settingsPage = init();
 
-      // For Kerberos page
-      settingsPage.showKerberosSection = true;
-
       const idleRender =
           settingsPage.shadowRoot.querySelector('settings-idle-load');
       await idleRender.get();
@@ -197,7 +195,11 @@
     [{
       pageName: 'reset',
       elementName: 'os-settings-reset-page',
-    }].forEach(({pageName, elementName}) => {
+    },
+     {
+       pageName: 'kerberos',
+       elementName: 'settings-kerberos-page',
+     }].forEach(({pageName, elementName}) => {
       test(`${pageName} page is controlled by pageAvailability`, () => {
         // Make page available
         settingsPage.pageAvailability = {
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_ui/os_settings_ui_test.ts b/chrome/test/data/webui/settings/chromeos/os_settings_ui/os_settings_ui_test.ts
index 49461ac3..a47339d 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_ui/os_settings_ui_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_ui/os_settings_ui_test.ts
@@ -5,6 +5,7 @@
 import {CrSettingsPrefs, OsSettingsMainElement, OsSettingsPageElement, OsSettingsSectionElement, OsSettingsUiElement} from 'chrome://os-settings/chromeos/os_settings.js';
 import {CrDrawerElement} from 'chrome://resources/cr_elements/cr_drawer/cr_drawer.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse, assertGT, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
@@ -19,6 +20,10 @@
 
   suiteSetup(async function() {
     document.body.innerHTML = '';
+    loadTimeData.overrideValues({
+      isKerberosEnabled: true,  // Simulate Kerberos enabled
+    });
+
     ui = document.createElement('os-settings-ui');
     document.body.appendChild(ui);
     flush();
@@ -30,9 +35,6 @@
     settingsPage = settingsMain.shadowRoot!.querySelector('os-settings-page');
     assert(settingsPage);
 
-    // Simulate Kerberos enabled.
-    settingsPage.showKerberosSection = true;
-
     const idleRender =
         settingsPage.shadowRoot!.querySelector('settings-idle-load');
     assert(idleRender);
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index e13929a..b485c5d 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -741,9 +741,9 @@
     });
 
 TEST_F(
-    'CrSettingsPrivacySandboxPageTest',
-    'PrivacySandboxNoticeRestrictedEnabledTests', function() {
-      runMochaSuite('PrivacySandboxNoticeRestrictedEnabledTests');
+    'CrSettingsPrivacySandboxPageTest', 'PrivacySandboxRestrictedEnabledTests',
+    function() {
+      runMochaSuite('PrivacySandboxRestrictedEnabledTests');
     });
 
 TEST_F('CrSettingsPrivacySandboxPageTest', 'TopicsSubpageTests', function() {
diff --git a/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts b/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts
index 9e02b61..a7d39c60 100644
--- a/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts
+++ b/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts
@@ -140,7 +140,7 @@
   });
 });
 
-suite('PrivacySandboxNoticeRestrictedEnabledTests', function() {
+suite('PrivacySandboxRestrictedEnabledTests', function() {
   let page: SettingsPrivacySandboxPageElement;
   let settingsPrefs: SettingsPrefsElement;
   let metricsBrowserProxy: TestMetricsBrowserProxy;
@@ -148,7 +148,6 @@
   suiteSetup(function() {
     loadTimeData.overrideValues({
       isPrivacySandboxRestricted: true,
-      isPrivacySandboxRestrictedNoticeEnabled: true,
     });
     settingsPrefs = document.createElement('settings-prefs');
     return CrSettingsPrefs.initialized;
@@ -166,7 +165,7 @@
     return flushTasks();
   });
 
-  // When the restricted notice is configured, ensure only measurement is shown.
+  // When the privacy sandbox is restricted, ensure only measurement is shown.
   test('privacySandboxLinkRowsNotVisible', function() {
     assertFalse(isChildVisible(page, '#privacySandboxTopicsLinkRow'));
     assertFalse(isChildVisible(page, '#privacySandboxFledgeLinkRow'));
diff --git a/chrome/test/variations/fixtures/skia_gold.py b/chrome/test/variations/fixtures/skia_gold.py
index 1e7f3d5..2d60ece 100644
--- a/chrome/test/variations/fixtures/skia_gold.py
+++ b/chrome/test/variations/fixtures/skia_gold.py
@@ -19,7 +19,6 @@
 # to the search path.
 from chrome.test.variations.test_utils import SRC_DIR
 sys.path.append(os.path.join(SRC_DIR, 'build'))
-from skia_gold_common import output_managerless_skia_gold_session as omsgs
 from skia_gold_common import skia_gold_properties as sgp
 from skia_gold_common import skia_gold_session_manager as sgsm
 from skia_gold_common.skia_gold_session import SkiaGoldSession
@@ -33,11 +32,6 @@
   skia_options, _ = parser.parse_known_args()
   return skia_options
 
-class _VariationsSkiaGoldSessionManager(sgsm.SkiaGoldSessionManager):
-  @staticmethod
-  def GetSessionClass():
-    return omsgs.OutputManagerlessSkiaGoldSession
-
 
 @attr.attrs()
 class VariationsSkiaGoldUtil:
@@ -90,7 +84,7 @@
 
   skia_gold_properties = sgp.SkiaGoldProperties(
     args=_get_skia_gold_args())
-  skia_gold_session_manager = _VariationsSkiaGoldSessionManager(
+  skia_gold_session_manager = SkiaGoldSessionManager(
     skia_tmp_dir,
     skia_gold_properties
   )
diff --git a/chrome/updater/app/app_server.cc b/chrome/updater/app/app_server.cc
index e504064..a40514fd 100644
--- a/chrome/updater/app/app_server.cc
+++ b/chrome/updater/app/app_server.cc
@@ -35,15 +35,11 @@
 
 namespace updater {
 
-namespace {
-
 bool IsInternalService() {
   return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
              kServerServiceSwitch) == kServerUpdateServiceInternalSwitchValue;
 }
 
-}  // namespace
-
 AppServer::AppServer() : external_constants_(CreateExternalConstants()) {}
 
 AppServer::~AppServer() = default;
diff --git a/chrome/updater/app/app_server.h b/chrome/updater/app/app_server.h
index 9030366..77fc767 100644
--- a/chrome/updater/app/app_server.h
+++ b/chrome/updater/app/app_server.h
@@ -19,6 +19,9 @@
 class UpdateService;
 struct RegistrationRequest;
 
+// Returns true if the command line has the switch `--service update-internal`.
+bool IsInternalService();
+
 // AppServer runs as the updater server process. Multiple servers of different
 // application versions can be run side-by-side. Each such server is called a
 // "candidate". Exactly one candidate is "active" at any given time. Only the
diff --git a/chrome/updater/app/server/win/service_main.cc b/chrome/updater/app/server/win/service_main.cc
index cc20475..f5eca87 100644
--- a/chrome/updater/app/server/win/service_main.cc
+++ b/chrome/updater/app/server/win/service_main.cc
@@ -18,6 +18,7 @@
 #include "base/process/launch.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/win/scoped_com_initializer.h"
+#include "chrome/updater/app/app_server.h"
 #include "chrome/updater/app/server/win/server.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/util/win_util.h"
@@ -30,11 +31,6 @@
 // debugging purposes.
 constexpr char kConsoleSwitchName[] = "console";
 
-bool IsInternalService() {
-  return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
-             kServerServiceSwitch) == kServerUpdateServiceInternalSwitchValue;
-}
-
 HRESULT RunWakeTask() {
   base::CommandLine run_updater_wake_command(
       base::CommandLine::ForCurrentProcess()->GetProgram());
diff --git a/chrome/updater/mac/install_from_archive.mm b/chrome/updater/mac/install_from_archive.mm
index af30a01..06a3f87 100644
--- a/chrome/updater/mac/install_from_archive.mm
+++ b/chrome/updater/mac/install_from_archive.mm
@@ -64,7 +64,7 @@
     command.AppendArg(arg);
 
   std::string output;
-  bool result = base::GetAppOutputAndError(command, &output);
+  bool result = base::GetAppOutput(command, &output);
   if (!result)
     VLOG(1) << "hdiutil failed.";
 
diff --git a/chrome/updater/mac/signing/pipeline.py b/chrome/updater/mac/signing/pipeline.py
index 6d3b842..05afcf1 100644
--- a/chrome/updater/mac/signing/pipeline.py
+++ b/chrome/updater/mac/signing/pipeline.py
@@ -113,6 +113,7 @@
 def sign_all(orig_paths,
              config,
              disable_packaging=False,
+             notarization=model.NotarizeAndStapleLevel.STAPLE,
              skip_brands=[],
              channels=[]):
     """Code signs, packages, and signs the package, placing the result into
@@ -123,6 +124,9 @@
         orig_paths: A |model.Paths| object.
         config: The |config.CodeSignConfig| object.
         disable_packaging: Ignored.
+        notarization: The level of notarization to be performed. If
+            |disable_packaging| is False, the dmg will undergo the same
+            notarization.
         skip_brands: Ignored.
         channels: Ignored.
     """
@@ -134,7 +138,7 @@
                                     config.packaging_basename)
             _sign_app(paths, config, dest_dir)
 
-            if config.notarize.should_notarize():
+            if notarization.should_notarize():
                 zip_file = os.path.join(notary_paths.work,
                                         config.packaging_basename + '.zip')
                 commands.run_command([
@@ -145,10 +149,10 @@
                 uuid = notarize.submit(zip_file, config)
 
         # Wait for the app notarization result to come back and staple.
-        if config.notarize.should_wait():
+        if notarization.should_wait():
             for _ in notarize.wait_for_results([uuid], config):
                 pass  # We are only waiting for a single notarization.
-            if config.notarize.should_staple():
+            if notarization.should_staple():
                 notarize.staple_bundled_parts(
                     # Only staple to the outermost app.
                     parts.get_parts(config)[-1:],
@@ -165,9 +169,9 @@
         dmg_path = _package_and_sign_dmg(package_paths, config)
 
         # Notarize the package, then staple.
-        if config.notarize.should_wait():
+        if notarization.should_wait():
             for _ in notarize.wait_for_results(
                 [notarize.submit(dmg_path, config)], config):
                 pass  # We are only waiting for a single notarization.
-            if config.notarize.should_staple():
+            if notarization.should_staple():
                 notarize.staple(dmg_path)
diff --git a/chrome/updater/policy/mac/managed_preference_policy_manager.mm b/chrome/updater/policy/mac/managed_preference_policy_manager.mm
index 31d6e203..ff3db99 100644
--- a/chrome/updater/policy/mac/managed_preference_policy_manager.mm
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager.mm
@@ -7,7 +7,7 @@
 #include <string>
 #include <vector>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
@@ -203,21 +203,21 @@
 
 NSDictionary* ReadManagedPreferencePolicyDictionary() {
   base::ScopedCFTypeRef<CFPropertyListRef> policies(CFPreferencesCopyAppValue(
-      base::mac::NSToCFPtrCast(kManagedPreferencesUpdatePolicies),
-      base::mac::NSToCFPtrCast(kKeystoneSharedPreferenceSuite)));
+      base::apple::NSToCFPtrCast(kManagedPreferencesUpdatePolicies),
+      base::apple::NSToCFPtrCast(kKeystoneSharedPreferenceSuite)));
   if (!policies)
     return nil;
 
   if (!CFPreferencesAppValueIsForced(
-          base::mac::NSToCFPtrCast(kManagedPreferencesUpdatePolicies),
-          base::mac::NSToCFPtrCast(kKeystoneSharedPreferenceSuite))) {
+          base::apple::NSToCFPtrCast(kManagedPreferencesUpdatePolicies),
+          base::apple::NSToCFPtrCast(kKeystoneSharedPreferenceSuite))) {
     return nil;
   }
 
   if (CFGetTypeID(policies) != CFDictionaryGetTypeID())
     return nil;
 
-  return base::mac::CFToNSOwnershipCast((CFDictionaryRef)policies.release());
+  return base::apple::CFToNSOwnershipCast((CFDictionaryRef)policies.release());
 }
 
 scoped_refptr<PolicyManagerInterface> CreateManagedPreferencePolicyManager() {
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index df3d616f..4c84b8a 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -289,6 +289,12 @@
                       L"*/components/update_client/*=2,"
                       L"*/chrome/updater/*=2"})
             .c_str());
+
+    EXPECT_EQ(task_info.trigger_types,
+              TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY |
+                  (IsSystemInstall(scope)
+                       ? TaskScheduler::TriggerType::TRIGGER_TYPE_POST_REBOOT
+                       : 0));
   } else {
     task_scheduler->ForEachTaskWithPrefix(
         base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
diff --git a/chrome/updater/update_usage_stats_task_unittest.cc b/chrome/updater/update_usage_stats_task_unittest.cc
index 0aaeabe..531efbc 100644
--- a/chrome/updater/update_usage_stats_task_unittest.cc
+++ b/chrome/updater/update_usage_stats_task_unittest.cc
@@ -111,14 +111,7 @@
   std::vector<base::ScopedClosureRunner> cleanups_;
 };
 
-// Mac Google-branded builds may pick up Chrome or other Google software
-// usagestat opt-ins from outside this test. Disable the test in that
-// configuration.
-#if !BUILDFLAG(IS_MAC) || !BUILDFLAG(GOOGLE_CHROME_BRANDING)
-#endif
-
 #if BUILDFLAG(IS_WIN)
-
 TEST_F(UpdateUsageStatsTaskTest, NoApps) {
   ClearAppUsageStats("app1");
   ClearAppUsageStats("app2");
@@ -158,8 +151,10 @@
   SetAppUsageStats(CLIENT_STATE_KEY, "app1", false);
   ASSERT_TRUE(OtherAppUsageStatsAllowed({"app1"}, GetTestScope()));
 }
-
 #elif !BUILDFLAG(IS_MAC) || !BUILDFLAG(GOOGLE_CHROME_BRANDING)
+// Mac Google-branded builds may pick up Chrome or other Google software
+// usagestat opt-ins from outside this test. Disable the test in that
+// configuration.
 TEST_F(UpdateUsageStatsTaskTest, NoApps) {
   ClearAppUsageStats("app1");
   ClearAppUsageStats("app2");
diff --git a/chrome/updater/win/setup/setup_util.cc b/chrome/updater/win/setup/setup_util.cc
index 357013b..a5ac51f 100644
--- a/chrome/updater/win/setup/setup_util.cc
+++ b/chrome/updater/win/setup/setup_util.cc
@@ -519,7 +519,11 @@
 
   if (!task_scheduler->RegisterTask(
           task_name.c_str(), GetTaskDisplayName(scope_).c_str(), run_command_,
-          TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY, true)) {
+          TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY |
+              (IsSystemInstall(scope_)
+                   ? TaskScheduler::TriggerType::TRIGGER_TYPE_POST_REBOOT
+                   : 0),
+          true)) {
     return false;
   }
 
diff --git a/chrome/updater/win/task_scheduler.cc b/chrome/updater/win/task_scheduler.cc
index d41e623..ff725e3 100644
--- a/chrome/updater/win/task_scheduler.cc
+++ b/chrome/updater/win/task_scheduler.cc
@@ -346,9 +346,10 @@
       return false;
     }
 
-    hr = GetTaskTriggerType(registered_task.Get(), &info_storage.trigger_type);
+    hr =
+        GetTaskTriggerTypes(registered_task.Get(), &info_storage.trigger_types);
     if (FAILED(hr)) {
-      LOG(ERROR) << "Failed to get trigger type for task '" << task_name
+      LOG(ERROR) << "Failed to get trigger types for task '" << task_name
                  << "'. " << std::hex << hr << ": "
                  << logging::SystemErrorCodeToString(hr);
       return false;
@@ -421,7 +422,7 @@
   bool RegisterTask(const wchar_t* task_name,
                     const wchar_t* task_description,
                     const base::CommandLine& run_command,
-                    TriggerType trigger_type,
+                    int trigger_types,
                     bool hidden) override {
     CHECK(task_name);
     CHECK(task_description);
@@ -537,105 +538,113 @@
       return false;
     }
 
-    TASK_TRIGGER_TYPE2 task_trigger_type = TASK_TRIGGER_EVENT;
-    base::win::ScopedBstr repetition_interval;
-    switch (trigger_type) {
-      case TRIGGER_TYPE_POST_REBOOT:
-        task_trigger_type = TASK_TRIGGER_LOGON;
-        break;
-      case TRIGGER_TYPE_NOW:
-        task_trigger_type = TASK_TRIGGER_REGISTRATION;
-        break;
-      case TRIGGER_TYPE_HOURLY:
-      case TRIGGER_TYPE_EVERY_FIVE_HOURS:
-        task_trigger_type = TASK_TRIGGER_DAILY;
-        if (trigger_type == TRIGGER_TYPE_EVERY_FIVE_HOURS) {
-          repetition_interval.Reset(::SysAllocString(kFiveHoursText));
-        } else if (trigger_type == TRIGGER_TYPE_HOURLY) {
-          repetition_interval.Reset(::SysAllocString(kOneHourText));
-        } else {
+    for (const TriggerType trigger_type :
+         {TRIGGER_TYPE_POST_REBOOT, TRIGGER_TYPE_NOW, TRIGGER_TYPE_HOURLY,
+          TRIGGER_TYPE_EVERY_FIVE_HOURS}) {
+      if (!(trigger_types & trigger_type)) {
+        continue;
+      }
+
+      TASK_TRIGGER_TYPE2 task_trigger_type = TASK_TRIGGER_EVENT;
+      base::win::ScopedBstr repetition_interval;
+      switch (trigger_type) {
+        case TRIGGER_TYPE_POST_REBOOT:
+          task_trigger_type = TASK_TRIGGER_LOGON;
+          break;
+        case TRIGGER_TYPE_NOW:
+          task_trigger_type = TASK_TRIGGER_REGISTRATION;
+          break;
+        case TRIGGER_TYPE_HOURLY:
+        case TRIGGER_TYPE_EVERY_FIVE_HOURS:
+          task_trigger_type = TASK_TRIGGER_DAILY;
+          if (trigger_type == TRIGGER_TYPE_EVERY_FIVE_HOURS) {
+            repetition_interval.Reset(::SysAllocString(kFiveHoursText));
+          } else if (trigger_type == TRIGGER_TYPE_HOURLY) {
+            repetition_interval.Reset(::SysAllocString(kOneHourText));
+          } else {
+            NOTREACHED() << "Unknown TriggerType?";
+          }
+          break;
+        default:
           NOTREACHED() << "Unknown TriggerType?";
+      }
+
+      Microsoft::WRL::ComPtr<ITrigger> trigger;
+      hr = trigger_collection->Create(task_trigger_type, &trigger);
+      if (FAILED(hr)) {
+        PLOG(ERROR) << "Can't create trigger of type " << task_trigger_type
+                    << ". " << std::hex << hr;
+        return false;
+      }
+
+      if (trigger_type == TRIGGER_TYPE_HOURLY ||
+          trigger_type == TRIGGER_TYPE_EVERY_FIVE_HOURS) {
+        Microsoft::WRL::ComPtr<IDailyTrigger> daily_trigger;
+        hr = trigger.As(&daily_trigger);
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't Query for registration trigger. " << std::hex
+                      << hr;
+          return false;
         }
-        break;
-      default:
-        NOTREACHED() << "Unknown TriggerType?";
-    }
 
-    Microsoft::WRL::ComPtr<ITrigger> trigger;
-    hr = trigger_collection->Create(task_trigger_type, &trigger);
-    if (FAILED(hr)) {
-      PLOG(ERROR) << "Can't create trigger of type " << task_trigger_type
-                  << ". " << std::hex << hr;
-      return false;
-    }
+        hr = daily_trigger->put_DaysInterval(1);
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't put 'DaysInterval' to 1, " << std::hex << hr;
+          return false;
+        }
 
-    if (trigger_type == TRIGGER_TYPE_HOURLY ||
-        trigger_type == TRIGGER_TYPE_EVERY_FIVE_HOURS) {
-      Microsoft::WRL::ComPtr<IDailyTrigger> daily_trigger;
-      hr = trigger.As(&daily_trigger);
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't Query for registration trigger. " << std::hex
-                    << hr;
-        return false;
+        Microsoft::WRL::ComPtr<IRepetitionPattern> repetition_pattern;
+        hr = trigger->get_Repetition(&repetition_pattern);
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't get 'Repetition'. " << std::hex << hr;
+          return false;
+        }
+
+        hr = repetition_pattern->put_Interval(repetition_interval.Get());
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't put 'Interval' to " << repetition_interval.Get()
+                      << ". " << std::hex << hr;
+          return false;
+        }
+
+        // The duration is the time to keep repeating intervals until the next
+        // daily trigger.
+        hr = repetition_pattern->put_Duration(
+            base::win::ScopedBstr(kOneDayText).Get());
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't put 'Duration' to " << kOneDayText << ". "
+                      << std::hex << hr;
+          return false;
+        }
+
+        // Start 5 minutes from the current time.
+        base::Time five_minutes_from_now(base::Time::NowFromSystemTime() +
+                                         base::Minutes(5));
+        base::win::ScopedBstr start_boundary(
+            GetTimestampString(five_minutes_from_now));
+        hr = trigger->put_StartBoundary(start_boundary.Get());
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't put 'StartBoundary' to " << start_boundary.Get()
+                      << ". " << std::hex << hr;
+          return false;
+        }
       }
 
-      hr = daily_trigger->put_DaysInterval(1);
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't put 'DaysInterval' to 1, " << std::hex << hr;
-        return false;
-      }
+      if (trigger_type == TRIGGER_TYPE_POST_REBOOT) {
+        Microsoft::WRL::ComPtr<ILogonTrigger> logon_trigger;
+        hr = trigger.As(&logon_trigger);
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't query trigger for 'ILogonTrigger'. " << std::hex
+                      << hr;
+          return false;
+        }
 
-      Microsoft::WRL::ComPtr<IRepetitionPattern> repetition_pattern;
-      hr = trigger->get_Repetition(&repetition_pattern);
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't get 'Repetition'. " << std::hex << hr;
-        return false;
-      }
-
-      hr = repetition_pattern->put_Interval(repetition_interval.Get());
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't put 'Interval' to " << repetition_interval.Get()
-                    << ". " << std::hex << hr;
-        return false;
-      }
-
-      // The duration is the time to keep repeating intervals until the next
-      // daily trigger.
-      hr = repetition_pattern->put_Duration(
-          base::win::ScopedBstr(kOneDayText).Get());
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't put 'Duration' to " << kOneDayText << ". "
-                    << std::hex << hr;
-        return false;
-      }
-
-      // Start 5 minutes from the current time.
-      base::Time five_minutes_from_now(base::Time::NowFromSystemTime() +
-                                       base::Minutes(5));
-      base::win::ScopedBstr start_boundary(
-          GetTimestampString(five_minutes_from_now));
-      hr = trigger->put_StartBoundary(start_boundary.Get());
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't put 'StartBoundary' to " << start_boundary.Get()
-                    << ". " << std::hex << hr;
-        return false;
-      }
-    }
-
-    if (trigger_type == TRIGGER_TYPE_POST_REBOOT) {
-      Microsoft::WRL::ComPtr<ILogonTrigger> logon_trigger;
-      hr = trigger.As(&logon_trigger);
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't query trigger for 'ILogonTrigger'. " << std::hex
-                    << hr;
-        return false;
-      }
-
-      hr = logon_trigger->put_Delay(
-          base::win::ScopedBstr(kFifteenMinutesText).Get());
-      if (FAILED(hr)) {
-        PLOG(ERROR) << "Can't put 'Delay'. " << std::hex << hr;
-        return false;
+        hr = logon_trigger->put_Delay(
+            base::win::ScopedBstr(kFifteenMinutesText).Get());
+        if (FAILED(hr)) {
+          PLOG(ERROR) << "Can't put 'Delay'. " << std::hex << hr;
+          return false;
+        }
       }
     }
 
@@ -1155,9 +1164,9 @@
     return ERROR_SUCCESS;
   }
 
-  HRESULT GetTaskTriggerType(IRegisteredTask* task, TriggerType* trigger_type) {
+  HRESULT GetTaskTriggerTypes(IRegisteredTask* task, int* trigger_types) {
     CHECK(task);
-    CHECK(trigger_type);
+    CHECK(trigger_types);
 
     Microsoft::WRL::ComPtr<ITaskDefinition> task_info;
     HRESULT hr = task->get_Definition(&task_info);
@@ -1175,60 +1184,71 @@
       return false;
     }
 
-    Microsoft::WRL::ComPtr<ITrigger> trigger;
-    hr = trigger_collection->get_Item(1, &trigger);
+    LONG trigger_count = 0;
+    hr = trigger_collection->get_Count(&trigger_count);
     if (FAILED(hr)) {
-      LOG(ERROR) << "Failed to get trigger: "
+      LOG(ERROR) << "Failed to get trigger collection count: "
                  << logging::SystemErrorCodeToString(hr);
       return false;
     }
 
-    TASK_TRIGGER_TYPE2 task_trigger_type = {};
-    hr = trigger->get_Type(&task_trigger_type);
-    if (FAILED(hr)) {
-      LOG(ERROR) << "Failed to get trigger type: "
-                 << logging::SystemErrorCodeToString(hr);
-      return false;
-    }
-
-    switch (task_trigger_type) {
-      case TASK_TRIGGER_LOGON:
-        *trigger_type = TRIGGER_TYPE_POST_REBOOT;
-        break;
-      case TASK_TRIGGER_REGISTRATION:
-        *trigger_type = TRIGGER_TYPE_NOW;
-        break;
-      case TASK_TRIGGER_DAILY: {
-        Microsoft::WRL::ComPtr<IRepetitionPattern> repetition_pattern;
-        hr = trigger->get_Repetition(&repetition_pattern);
-        if (FAILED(hr)) {
-          LOG(ERROR) << "Failed to get 'Repetition'. "
-                     << logging::SystemErrorCodeToString(hr);
-          return false;
-        }
-
-        base::win::ScopedBstr repetition_interval;
-        hr = repetition_pattern->get_Interval(repetition_interval.Receive());
-        if (FAILED(hr)) {
-          LOG(ERROR) << "Failed to get 'Interval': "
-                     << logging::SystemErrorCodeToString(hr);
-          return false;
-        }
-
-        if (base::EqualsCaseInsensitiveASCII(repetition_interval.Get(),
-                                             kFiveHoursText)) {
-          *trigger_type = TRIGGER_TYPE_EVERY_FIVE_HOURS;
-        } else if (base::EqualsCaseInsensitiveASCII(repetition_interval.Get(),
-                                                    kOneHourText)) {
-          *trigger_type = TRIGGER_TYPE_HOURLY;
-        } else {
-          NOTREACHED() << "Unknown TriggerType for interval: "
-                       << repetition_interval.Get();
-        }
-        break;
+    *trigger_types = 0;
+    for (LONG t = 1; t <= trigger_count; ++t) {
+      Microsoft::WRL::ComPtr<ITrigger> trigger;
+      hr = trigger_collection->get_Item(t, &trigger);
+      if (FAILED(hr)) {
+        LOG(ERROR) << "Failed to get trigger: "
+                   << logging::SystemErrorCodeToString(hr);
+        return false;
       }
-      default:
-        NOTREACHED() << "Unknown task trigger type: " << task_trigger_type;
+
+      TASK_TRIGGER_TYPE2 task_trigger_type = {};
+      hr = trigger->get_Type(&task_trigger_type);
+      if (FAILED(hr)) {
+        LOG(ERROR) << "Failed to get trigger type: "
+                   << logging::SystemErrorCodeToString(hr);
+        return false;
+      }
+
+      switch (task_trigger_type) {
+        case TASK_TRIGGER_LOGON:
+          *trigger_types |= TRIGGER_TYPE_POST_REBOOT;
+          break;
+        case TASK_TRIGGER_REGISTRATION:
+          *trigger_types |= TRIGGER_TYPE_NOW;
+          break;
+        case TASK_TRIGGER_DAILY: {
+          Microsoft::WRL::ComPtr<IRepetitionPattern> repetition_pattern;
+          hr = trigger->get_Repetition(&repetition_pattern);
+          if (FAILED(hr)) {
+            LOG(ERROR) << "Failed to get 'Repetition'. "
+                       << logging::SystemErrorCodeToString(hr);
+            return false;
+          }
+
+          base::win::ScopedBstr repetition_interval;
+          hr = repetition_pattern->get_Interval(repetition_interval.Receive());
+          if (FAILED(hr)) {
+            LOG(ERROR) << "Failed to get 'Interval': "
+                       << logging::SystemErrorCodeToString(hr);
+            return false;
+          }
+
+          if (base::EqualsCaseInsensitiveASCII(repetition_interval.Get(),
+                                               kFiveHoursText)) {
+            *trigger_types |= TRIGGER_TYPE_EVERY_FIVE_HOURS;
+          } else if (base::EqualsCaseInsensitiveASCII(repetition_interval.Get(),
+                                                      kOneHourText)) {
+            *trigger_types |= TRIGGER_TYPE_HOURLY;
+          } else {
+            NOTREACHED() << "Unknown TriggerType for interval: "
+                         << repetition_interval.Get();
+          }
+          break;
+        }
+        default:
+          NOTREACHED() << "Unknown task trigger type: " << task_trigger_type;
+      }
     }
 
     return S_OK;
diff --git a/chrome/updater/win/task_scheduler.h b/chrome/updater/win/task_scheduler.h
index ecfa0c0..c9702331 100644
--- a/chrome/updater/win/task_scheduler.h
+++ b/chrome/updater/win/task_scheduler.h
@@ -30,13 +30,14 @@
 // before calling |Register|, or to verify its existence, or delete it.
 class TaskScheduler : public base::RefCountedThreadSafe<TaskScheduler> {
  public:
-  // The type of trigger to register for this task.
+  // The types of trigger to register for this task. Multiple triggers types can
+  // be combined using the bitwise OR operator.
   enum TriggerType {
-    TRIGGER_TYPE_POST_REBOOT = 0,  // Only run once post-reboot.
-    TRIGGER_TYPE_NOW = 1,          // Run right now (mainly for tests).
-    TRIGGER_TYPE_HOURLY = 2,       // Run every hour.
-    TRIGGER_TYPE_EVERY_FIVE_HOURS = 3,
-    TRIGGER_TYPE_MAX,
+    TRIGGER_TYPE_POST_REBOOT = 1 << 0,  // Only run once post-reboot.
+    TRIGGER_TYPE_NOW = 1 << 1,          // Run right now (mainly for tests).
+    TRIGGER_TYPE_HOURLY = 1 << 2,       // Run every hour.
+    TRIGGER_TYPE_EVERY_FIVE_HOURS = 1 << 3,
+    TRIGGER_TYPE_MAX = 1 << 4,
   };
 
   // The log-on requirements for a task to be scheduled. Note that a task can
@@ -93,7 +94,7 @@
     // User ID under which the task runs.
     std::wstring user_id;
 
-    TriggerType trigger_type = TRIGGER_TYPE_MAX;
+    int trigger_types = 0;
   };
 
   // Creates an instance of the task scheduler for the given `scope`.
@@ -147,11 +148,12 @@
   virtual bool HasTaskFolder(const wchar_t* folder_name) = 0;
 
   // Register the task to run the specified application and using the given
-  // |trigger_type|.
+  // `trigger_types`. `trigger_types` is a bitwise OR of one or more types in
+  // the `TriggerType` enum.
   virtual bool RegisterTask(const wchar_t* task_name,
                             const wchar_t* task_description,
                             const base::CommandLine& run_command,
-                            TriggerType trigger_type,
+                            int trigger_types,
                             bool hidden) = 0;
 
   // Returns true if the scheduled task specified by |task_name| can be started
diff --git a/chrome/updater/win/task_scheduler_unittest.cc b/chrome/updater/win/task_scheduler_unittest.cc
index a9822bb59..48670205 100644
--- a/chrome/updater/win/task_scheduler_unittest.cc
+++ b/chrome/updater/win/task_scheduler_unittest.cc
@@ -112,7 +112,7 @@
     // Check that the created task matches the trigger it was created with.
     TaskScheduler::TaskInfo info;
     EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
-    EXPECT_EQ(info.trigger_type, trigger_type);
+    EXPECT_EQ(info.trigger_types, trigger_type);
 
     if (trigger_type != TaskScheduler::TRIGGER_TYPE_NOW) {
       EXPECT_TRUE(task_scheduler_->StartTask(kTaskName1));
@@ -138,17 +138,16 @@
     test::PrintLog(GetTestScope());
   }
 
-  void RunGetTaskInfoTriggerTypeTest(
-      TaskScheduler::TriggerType expected_trigger_type) {
+  void RunGetTaskInfoTriggerTypesTest(int expected_trigger_types) {
     base::CommandLine command_line =
         GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
 
     EXPECT_TRUE(task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1,
                                               command_line,
-                                              expected_trigger_type, false));
+                                              expected_trigger_types, false));
     TaskScheduler::TaskInfo info;
     EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
-    EXPECT_EQ(info.trigger_type, expected_trigger_type);
+    EXPECT_EQ(info.trigger_types, expected_trigger_types);
     EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
   }
 
@@ -210,7 +209,7 @@
   // Check that the task has a hourly trigger.
   TaskScheduler::TaskInfo info;
   EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
-  EXPECT_EQ(info.trigger_type, TaskScheduler::TRIGGER_TYPE_HOURLY);
+  EXPECT_EQ(info.trigger_types, TaskScheduler::TRIGGER_TYPE_HOURLY);
 
   EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
   EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName1));
@@ -237,7 +236,7 @@
   // Check that the task has a five hour trigger.
   TaskScheduler::TaskInfo info;
   EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
-  EXPECT_EQ(info.trigger_type, TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS);
+  EXPECT_EQ(info.trigger_types, TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS);
 
   EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
   EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName1));
@@ -457,7 +456,7 @@
                              base::CompareCase::INSENSITIVE_ASCII));
 }
 
-TEST_F(TaskSchedulerTests, GetTaskInfoTriggerType) {
+TEST_F(TaskSchedulerTests, GetTaskInfoTriggerTypes) {
   for (const TaskScheduler::TriggerType expected_trigger_type : {
            TaskScheduler::TRIGGER_TYPE_POST_REBOOT,
            TaskScheduler::TRIGGER_TYPE_HOURLY,
@@ -468,8 +467,12 @@
       continue;
     }
 
-    RunGetTaskInfoTriggerTypeTest(expected_trigger_type);
+    RunGetTaskInfoTriggerTypesTest(expected_trigger_type);
   }
+
+  RunGetTaskInfoTriggerTypesTest(TaskScheduler::TRIGGER_TYPE_POST_REBOOT |
+                                 TaskScheduler::TRIGGER_TYPE_HOURLY |
+                                 TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS);
 }
 
 TEST(TaskSchedulerTest, NoSubfolders) {
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index fedf219..6a25a5b 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15458.0.0
\ No newline at end of file
+15459.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/multidevice/remote_device_ref.h b/chromeos/ash/components/multidevice/remote_device_ref.h
index edf9ce2..e57e9f0 100644
--- a/chromeos/ash/components/multidevice/remote_device_ref.h
+++ b/chromeos/ash/components/multidevice/remote_device_ref.h
@@ -14,7 +14,7 @@
 #include "chromeos/ash/components/multidevice/software_feature_state.h"
 
 namespace ash {
-class EasyUnlockServiceRegular;
+class EasyUnlockService;
 namespace multidevice_setup {
 class MultiDeviceSetupImpl;
 }
@@ -101,7 +101,7 @@
 
   // TODO(crbug.com/752273): Remove these once clients have migrated to Device
   // Sync service.
-  friend class EasyUnlockServiceRegular;
+  friend class EasyUnlockService;
   friend class tether::TetherHostFetcherImplTest;
   friend class tether::TetherHostFetcherImpl;
   friend class ProximityAuthWebUIHandler;
diff --git a/chromeos/ash/components/network/metrics/hotspot_metrics_helper.h b/chromeos/ash/components/network/metrics/hotspot_metrics_helper.h
index 6d0d970..4288300 100644
--- a/chromeos/ash/components/network/metrics/hotspot_metrics_helper.h
+++ b/chromeos/ash/components/network/metrics/hotspot_metrics_helper.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_HOTSPOT_METRICS_HELPER_H_
 
 #include "base/component_export.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
diff --git a/chromeos/ash/components/proximity_auth/screenlock_bridge.h b/chromeos/ash/components/proximity_auth/screenlock_bridge.h
index fd9bf7b8..e800837 100644
--- a/chromeos/ash/components/proximity_auth/screenlock_bridge.h
+++ b/chromeos/ash/components/proximity_auth/screenlock_bridge.h
@@ -142,7 +142,8 @@
     // Invoked after the screen lock is dismissed.
     virtual void OnScreenDidUnlock(LockHandler::ScreenType screen_type) = 0;
 
-    // Invoked when the user focused on the lock screen changes.
+    // TODO(b/227674947): This method isn't being used anywhere and can be
+    // deleted. Invoked when the user focused on the lock screen changes.
     virtual void OnFocusedUserChanged(const AccountId& account_id) = 0;
 
    protected:
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index d286117..0cf665f 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -308,7 +308,7 @@
 
       <!-- Multitask Menu -->
       <message name="IDS_MULTITASK_MENU_HALF_BUTTON_NAME" desc="Title of the half button on the multitask menu.">
-        Half
+        Split
       </message>
       <message name="IDS_MULTITASK_MENU_PARTIAL_BUTTON_NAME" desc="Title and and accessible name of the partial button on the multitask menu.">
         Partial
@@ -648,6 +648,9 @@
         <message name="IDS_PRINT_MANAGEMENT_CLIENT_UNAUTHORIZED_ERROR_STATUS" desc="The error status displayed next to a print job to indicate to users that their print job failed because their authorization with printer has failed.">
           Failed - Authorization Failed
         </message>
+        <message name="IDS_PRINT_MANAGEMENT_EXPIRED_CERTIFICATE_ERROR_STATUS" desc="The error status displayed next to a print job to indicate to users that their print job failed because their printer has an expired SSL certificate.">
+          Failed - Certificate Expired
+        </message>
         <message name="IDS_PRINT_MANAGEMENT_UNKNOWN_ERROR_STATUS" desc="The error status displayed next to a print job to indicate to users that their print job failed due to an unknown error.">
           Failed - Unknown error
         </message>
diff --git a/chromeos/chromeos_strings_grd/IDS_MULTITASK_MENU_HALF_BUTTON_NAME.png.sha1 b/chromeos/chromeos_strings_grd/IDS_MULTITASK_MENU_HALF_BUTTON_NAME.png.sha1
index c44e2b34..906aca7 100644
--- a/chromeos/chromeos_strings_grd/IDS_MULTITASK_MENU_HALF_BUTTON_NAME.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_MULTITASK_MENU_HALF_BUTTON_NAME.png.sha1
@@ -1 +1 @@
-eda575ec10563f749001a1e417a15d117b251e29
\ No newline at end of file
+6a448a9fdbba937ac2422906a6b863c44d242cf1
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PRINT_MANAGEMENT_EXPIRED_CERTIFICATE_ERROR_STATUS.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PRINT_MANAGEMENT_EXPIRED_CERTIFICATE_ERROR_STATUS.png.sha1
new file mode 100644
index 0000000..35a7223
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PRINT_MANAGEMENT_EXPIRED_CERTIFICATE_ERROR_STATUS.png.sha1
@@ -0,0 +1 @@
+bf6b86e4b6ba2255fa01439297e73f3acb1eec34
\ No newline at end of file
diff --git a/chromeos/components/print_management/mojom/printing_manager.mojom b/chromeos/components/print_management/mojom/printing_manager.mojom
index 983a6e5..5902122 100644
--- a/chromeos/components/print_management/mojom/printing_manager.mojom
+++ b/chromeos/components/print_management/mojom/printing_manager.mojom
@@ -30,6 +30,7 @@
   kFilterFailed,
   kUnknownError,
   kClientUnauthorized,
+  kExpiredCertificate,
 };
 
 // Contains information about a completed print job. Completed print jobs are
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index 40e3c4b..5378bbf 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -871,28 +871,25 @@
   // Uses the Lacros preference SessionStartupPref.
   [Default] kUseStartupPreference = 0,
 
-  // TODO(hidehiko): This should be kOpenNTPIncognitoWindow
-  kOpenIncognitoWindow = 1,
+  // Deprecated in M114. No longer handled by Lacros, do not use.
+  kOpenIncognitoWindowDeprecated = 1,
 
-  // TODO(hidehiko): This should be kRestoreLastTab.
-  kRestoreLastSession = 2,
+  // Deprecated in M114. No longer handled by Lacros, do not use.
+  kRestoreLastSessionDeprecated = 2,
 
   // Lacros will launch but will not show any windows. It will continue running
   // in the background until a browser window is shown. At that point it will no
   // longer run in the background and will close when all windows are closed.
   [MinVersion=1] kDoNotOpenWindow = 3,
 
-  // Opens a new window with a new tab, regardless of users' preference.
-  [MinVersion=2] kOpenNewTabPageWindow = 4,
+  // Deprecated in M114. No longer handled by Lacros, do not use.
+  [MinVersion=2] kOpenNewTabPageWindowDeprecated = 4,
 
-  // Opens a new window with specified URLs with the main profile.
-  // The URLs should be set with BrowserInitParams::startup_urls.
-  // Added in M96.
-  [MinVersion=3] kOpenWindowWithUrls = 5,
+  // Deprecated in M114. No longer handled by Lacros, do not use.
+  [MinVersion=3] kOpenWindowWithUrlsDeprecated = 5,
 
-  // Opens a new Guest mode window.
-  // Added in M100.
-  [MinVersion=4] kOpenGuestWindow = 6,
+  // Deprecated in M114. No longer handled by Lacros, do not use.
+  [MinVersion=4] kOpenGuestWindowDeprecated = 6,
 };
 
 // Whether / how mlservice on-device handwriting is supported.
@@ -1109,7 +1106,7 @@
   // initial_browser_action == OpenWindowWithUrls.
   // Added in M96.
   [MinVersion=24]
-  array<url.mojom.Url>? startup_urls@24;
+  array<url.mojom.Url>? REMOVED_24@24;
 
   // The set of device settings for Lacros. Lacros should *NOT* use this data,
   // but rely on DeviceSettingsLacros::GetDeviceSettings instead.
@@ -1419,7 +1416,7 @@
   // URLs to be opened at the beginning. This works only when
   // initial_browser_action == OpenWindowWithUrls.
   // Added in M96.
-  array<url.mojom.Url>? startup_urls@11;
+  array<url.mojom.Url>? REMOVED_11@11;
 
   // When this flag is |true|, Lacros is the only browser.
   bool standalone_browser_is_only_browser@12;
diff --git a/chromeos/crosapi/mojom/local_printer.mojom b/chromeos/crosapi/mojom/local_printer.mojom
index 2d631136..d723e34 100644
--- a/chromeos/crosapi/mojom/local_printer.mojom
+++ b/chromeos/crosapi/mojom/local_printer.mojom
@@ -81,6 +81,7 @@
     kPrinterUnreachable,
     kStopped,
     kTrayMissing,
+    [MinVersion=1] kExpiredCertificate,
   };
   [Stable, Extensible]
   enum Severity {
diff --git a/chromeos/startup/browser_params_proxy.cc b/chromeos/startup/browser_params_proxy.cc
index 26e7d473..d4cdd7d 100644
--- a/chromeos/startup/browser_params_proxy.cc
+++ b/chromeos/startup/browser_params_proxy.cc
@@ -137,13 +137,6 @@
   return BrowserInitParams::Get()->startup_urls_from;
 }
 
-const absl::optional<std::vector<GURL>>& BrowserParamsProxy::StartupUrls()
-    const {
-  if (IsLaunchedWithPostLoginParams())
-    return BrowserPostLoginParams::Get()->startup_urls;
-  return BrowserInitParams::Get()->startup_urls;
-}
-
 const crosapi::mojom::DeviceSettingsPtr& BrowserParamsProxy::DeviceSettings()
     const {
   return BrowserInitParams::Get()->device_settings;
diff --git a/chromeos/startup/browser_params_proxy.h b/chromeos/startup/browser_params_proxy.h
index 43aef9a..34e48ba 100644
--- a/chromeos/startup/browser_params_proxy.h
+++ b/chromeos/startup/browser_params_proxy.h
@@ -65,8 +65,6 @@
 
   crosapi::mojom::OpenUrlFrom StartupUrlsFrom() const;
 
-  const absl::optional<std::vector<GURL>>& StartupUrls() const;
-
   const crosapi::mojom::DeviceSettingsPtr& DeviceSettings() const;
 
   const absl::optional<std::string>& MetricsServiceClientId() const;
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
index 0a5c7d26..d2e25e3 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
@@ -26,8 +26,10 @@
 #include "ui/base/default_style.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/compositor/layer.h"
 #include "ui/display/screen.h"
 #include "ui/events/types/event_type.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
@@ -46,6 +48,9 @@
 // after 250 ms have elapsed.
 constexpr base::TimeDelta kMouseExitMenuTimeout = base::Milliseconds(250);
 
+// The multitask menu fade out duration after the exit timer finishes.
+constexpr base::TimeDelta kFadeDuration = base::Milliseconds(100);
+
 // Creates multitask button with label.
 std::unique_ptr<views::View> CreateButtonContainer(
     std::unique_ptr<views::View> button_view,
@@ -92,6 +97,7 @@
 
     if (event->type() == ui::ET_MOUSE_PRESSED) {
       ProcessPressedEvent(*event);
+      return;
     }
 
     if (event->type() == ui::ET_MOUSE_MOVED && anchor_view_) {
@@ -130,7 +136,20 @@
   }
 
  private:
-  void OnExitTimerFinished() { close_callback_.Run(); }
+  void OnExitTimerFinished() {
+    if (!menu_widget_->GetLayer()->GetAnimator()->is_animating()) {
+      views::AnimationBuilder()
+          .SetPreemptionStrategy(
+              ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+          .OnEnded(base::BindOnce(&MenuPreTargetHandler::OnFadeOutFinished,
+                                  weak_factory_.GetWeakPtr()))
+          .Once()
+          .SetDuration(kFadeDuration)
+          .SetOpacity(menu_widget_->GetLayer(), 0.0f, gfx::Tween::LINEAR);
+    }
+  }
+
+  void OnFadeOutFinished() { close_callback_.Run(); }
 
   // The widget of the multitask menu that is currently shown. Guaranteed to
   // outlive `this`, which will get destroyed when the menu is destructed in
@@ -144,6 +163,10 @@
   base::OneShotTimer exit_timer_;
 
   base::RepeatingClosure close_callback_;
+
+  // Chrome's compiler toolchain enforces that any `WeakPtrFactory`
+  // fields are declared last, to avoid destruction ordering issues.
+  base::WeakPtrFactory<MenuPreTargetHandler> weak_factory_{this};
 };
 
 // -----------------------------------------------------------------------------
diff --git a/components/access_code_cast/common/BUILD.gn b/components/access_code_cast/common/BUILD.gn
index 0687c5e..dbad64e 100644
--- a/components/access_code_cast/common/BUILD.gn
+++ b/components/access_code_cast/common/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-static_library("common") {
+static_library("metrics") {
   sources = [
     "access_code_cast_metrics.cc",
     "access_code_cast_metrics.h",
@@ -15,10 +15,8 @@
   sources = [ "access_code_cast_metrics_unittest.cc" ]
   deps = [
     "//base/test:test_support",
-    "//components/access_code_cast/common",
+    "//components/access_code_cast/common:metrics",
     "//testing/gtest",
   ]
-  data = [
-    "//tools/metrics/histograms/enums.xml",
-  ]
+  data = [ "//tools/metrics/histograms/enums.xml" ]
 }
diff --git a/components/access_code_cast/common/access_code_cast_metrics.cc b/components/access_code_cast/common/access_code_cast_metrics.cc
index 3f57492..81e052c 100644
--- a/components/access_code_cast/common/access_code_cast_metrics.cc
+++ b/components/access_code_cast/common/access_code_cast_metrics.cc
@@ -27,6 +27,10 @@
     "AccessCodeCast.Ui.DialogLoadTime";
 const char AccessCodeCastMetrics::kHistogramDialogOpenLocation[] =
     "AccessCodeCast.Ui.DialogOpenLocation";
+const char AccessCodeCastMetrics::kHistogramFreezeCount[] =
+    "AccessCodeCast.Session.FreezeCount";
+const char AccessCodeCastMetrics::kHistogramFreezeDuration[] =
+    "AccessCodeCast.Session.FreezeDuration";
 const char AccessCodeCastMetrics::kHistogramRememberedDevicesCount[] =
     "AccessCodeCast.Discovery.RememberedDevicesCount";
 const char AccessCodeCastMetrics::kHistogramRouteDiscoveryTypeAndSource[] =
@@ -144,6 +148,17 @@
 }
 
 // static
+void AccessCodeCastMetrics::RecordMirroringPauseCount(int count) {
+  base::UmaHistogramCounts100(kHistogramFreezeCount, count);
+}
+
+// static
+void AccessCodeCastMetrics::RecordMirroringPauseDuration(
+    base::TimeDelta duration) {
+  base::UmaHistogramLongTimes(kHistogramFreezeDuration, duration);
+}
+
+// static
 void AccessCodeCastMetrics::RecordRememberedDevicesCount(int count) {
   base::UmaHistogramCounts100(kHistogramRememberedDevicesCount, count);
 }
diff --git a/components/access_code_cast/common/access_code_cast_metrics.h b/components/access_code_cast/common/access_code_cast_metrics.h
index 1a353e6..bd54f22 100644
--- a/components/access_code_cast/common/access_code_cast_metrics.h
+++ b/components/access_code_cast/common/access_code_cast_metrics.h
@@ -115,6 +115,8 @@
   static const char kHistogramDialogCloseReason[];
   static const char kHistogramDialogLoadTime[];
   static const char kHistogramDialogOpenLocation[];
+  static const char kHistogramFreezeCount[];
+  static const char kHistogramFreezeDuration[];
   static const char kHistogramRememberedDevicesCount[];
   static const char kHistogramRouteDiscoveryTypeAndSource[];
   static const char kHistogramRouteDuration[];
@@ -149,6 +151,13 @@
   static void RecordDialogOpenLocation(
       AccessCodeCastDialogOpenLocation location);
 
+  // Records the number of times a mirroring session is paused during its
+  // duration.
+  static void RecordMirroringPauseCount(int count);
+
+  // Records the duration of time that a mirroring session is paused.
+  static void RecordMirroringPauseDuration(base::TimeDelta duration);
+
   // Records the count of cast devices which are currently being remembered
   // being the AccessCodeCastSinkService.
   static void RecordRememberedDevicesCount(int count);
diff --git a/components/access_code_cast/common/access_code_cast_metrics_unittest.cc b/components/access_code_cast/common/access_code_cast_metrics_unittest.cc
index b25347ab..3a7c818 100644
--- a/components/access_code_cast/common/access_code_cast_metrics_unittest.cc
+++ b/components/access_code_cast/common/access_code_cast_metrics_unittest.cc
@@ -267,6 +267,43 @@
   histogram_tester.ExpectTotalCount(histogram, 3);
 }
 
+TEST(AccessCodeCastMetricsTest, RecordMirroringPauseCount) {
+  base::HistogramTester histogram_tester;
+  char histogram[] = "AccessCodeCast.Session.FreezeCount";
+
+  AccessCodeCastMetrics::RecordMirroringPauseCount(0);
+  histogram_tester.ExpectBucketCount(histogram, 0, 1);
+
+  AccessCodeCastMetrics::RecordMirroringPauseCount(1);
+  histogram_tester.ExpectBucketCount(histogram, 1, 1);
+
+  AccessCodeCastMetrics::RecordMirroringPauseCount(100);
+  histogram_tester.ExpectBucketCount(histogram, 100, 1);
+
+  // Over 100 should be reported as 100.
+  AccessCodeCastMetrics::RecordMirroringPauseCount(500);
+  histogram_tester.ExpectBucketCount(histogram, 100, 2);
+
+  histogram_tester.ExpectTotalCount(histogram, 4);
+}
+
+TEST(AccessCodeCastMetricsTest, RecordMirroringPauseDuration) {
+  base::HistogramTester histogram_tester;
+  char histogram[] = "AccessCodeCast.Session.FreezeDuration";
+
+  AccessCodeCastMetrics::RecordMirroringPauseDuration(base::Milliseconds(1));
+  histogram_tester.ExpectTimeBucketCount(histogram, base::Milliseconds(1), 1);
+
+  AccessCodeCastMetrics::RecordMirroringPauseDuration(base::Minutes(5));
+  histogram_tester.ExpectTimeBucketCount(histogram, base::Minutes(5), 1);
+
+  AccessCodeCastMetrics::RecordMirroringPauseDuration(base::Hours(2));
+  // The long times histogram has a maximum value of 1 hours.
+  histogram_tester.ExpectTimeBucketCount(histogram, base::Hours(1), 1);
+
+  histogram_tester.ExpectTotalCount(histogram, 3);
+}
+
 TEST(AccessCodeCastMetricsTest, CheckMetricsEnums) {
   base::HistogramTester histogram_tester;
 
diff --git a/components/autofill/content/common/mojom/autofill_agent.mojom b/components/autofill/content/common/mojom/autofill_agent.mojom
index a2d86b0..b05253d 100644
--- a/components/autofill/content/common/mojom/autofill_agent.mojom
+++ b/components/autofill/content/common/mojom/autofill_agent.mojom
@@ -111,10 +111,10 @@
   // logging the decisions made about saving the password.
   SetLoggingState(bool active);
 
-  // Informs the renderer that the Touch To Fill sheet has been closed.
+  // Informs the renderer that the Keyboard Replacing Surface has been closed.
   // Indicates whether the virtual keyboard should be shown instead.
   [EnableIf=is_android]
-  TouchToFillClosed(bool show_virtual_keyboard);
+  KeyboardReplacingSurfaceClosed(bool show_virtual_keyboard);
 
   // Triggers a form submission on the last interacted element.
   [EnableIf=is_android]
diff --git a/components/autofill/content/common/mojom/autofill_driver.mojom b/components/autofill/content/common/mojom/autofill_driver.mojom
index a89adff..8dc46d5 100644
--- a/components/autofill/content/common/mojom/autofill_driver.mojom
+++ b/components/autofill/content/common/mojom/autofill_driver.mojom
@@ -149,10 +149,10 @@
                           mojo_base.mojom.String16 typed_username,
                           int32 options, gfx.mojom.RectF bounds);
 
-  // Instructs the browser to show the Touch To Fill UI and whether the form is
-  // ready for submission after filling.
+  // Instructs the browser to show a keyboard replacing surface (e.g.
+  // TouchToFill) and whether the form is ready for submission after filling.
   [EnableIf=is_android]
-  ShowTouchToFill(SubmissionReadinessState submission_readiness);
+  ShowKeyboardReplacingSurface(SubmissionReadinessState submission_readiness);
 
   // Checks the safe browsing reputation of the website where the focused
   // username/password field is on.
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 2e6e19e..639212e 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -1171,7 +1171,7 @@
     return;
 
 #if BUILDFLAG(IS_ANDROID)
-  password_autofill_agent_->TryToShowTouchToFill(element);
+  password_autofill_agent_->TryToShowKeyboardReplacingSurface(element);
 #endif
 
   ShowSuggestions(
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 57a23e08..6a798b4 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -606,9 +606,9 @@
              text_direction, typed_username, options, bounds);
   }
 #if BUILDFLAG(IS_ANDROID)
-  void ShowTouchToFill(
+  void ShowKeyboardReplacingSurface(
       mojom::SubmissionReadinessState submission_readiness) override {
-    DeferMsg(&mojom::PasswordManagerDriver::ShowTouchToFill,
+    DeferMsg(&mojom::PasswordManagerDriver::ShowKeyboardReplacingSurface,
              submission_readiness);
   }
 #endif
@@ -1018,14 +1018,18 @@
 
 #if BUILDFLAG(IS_ANDROID)
 bool PasswordAutofillAgent::ShouldSuppressKeyboard() {
-  // The keyboard should be suppressed if we are showing the Touch To Fill UI.
-  return touch_to_fill_state_ == TouchToFillState::kIsShowing;
+  // The keyboard should be suppressed if a keyboard replacing surface is
+  // displayed (e.g. TouchToFill).
+  return keyboard_replacing_surface_state_ ==
+         KeyboardReplacingSurfaceState::kIsShowing;
 }
 
-bool PasswordAutofillAgent::TryToShowTouchToFill(
+bool PasswordAutofillAgent::TryToShowKeyboardReplacingSurface(
     const WebFormControlElement& control_element) {
-  if (touch_to_fill_state_ != TouchToFillState::kShouldShow)
+  if (keyboard_replacing_surface_state_ !=
+      KeyboardReplacingSurfaceState::kShouldShow) {
     return false;
+  }
 
   const WebInputElement input_element =
       control_element.DynamicTo<WebInputElement>();
@@ -1061,12 +1065,12 @@
   std::unique_ptr<FormData> form_data =
       form.IsNull() ? GetFormDataFromUnownedInputElements()
                     : GetFormDataFromWebForm(form);
-  GetPasswordManagerDriver().ShowTouchToFill(
+  GetPasswordManagerDriver().ShowKeyboardReplacingSurface(
       form_data ? CalculateSubmissionReadiness(*form_data, username_element,
                                                password_element)
                 : mojom::SubmissionReadinessState::kNoInformation);
 
-  touch_to_fill_state_ = TouchToFillState::kIsShowing;
+  keyboard_replacing_surface_state_ = KeyboardReplacingSurfaceState::kIsShowing;
   return true;
 }
 #endif
@@ -1101,12 +1105,14 @@
     return false;
 
 #if BUILDFLAG(IS_ANDROID)
-  // Don't call ShowSuggestionPopup if Touch To Fill is currently showing. Since
-  // Touch To Fill in spirit is very similar to a suggestion pop-up, return true
-  // so that the AutofillAgent does not try to show other autofill suggestions
-  // instead.
-  if (touch_to_fill_state_ == TouchToFillState::kIsShowing)
+  // Don't call ShowSuggestionPopup if a keyboard replacing surface is currently
+  // showing. Since a keyboard replacing surface in spirit is very similar to a
+  // suggestion pop-up, return true so that the AutofillAgent does not try to
+  // show other autofill suggestions instead.
+  if (keyboard_replacing_surface_state_ ==
+      KeyboardReplacingSurfaceState::kIsShowing) {
     return true;
+  }
 #endif
 
   if (!HasDocumentWithValidFrame(element))
@@ -1519,12 +1525,14 @@
 }
 
 #if BUILDFLAG(IS_ANDROID)
-void PasswordAutofillAgent::TouchToFillClosed(bool show_virtual_keyboard) {
-  touch_to_fill_state_ = TouchToFillState::kWasShown;
+void PasswordAutofillAgent::KeyboardReplacingSurfaceClosed(
+    bool show_virtual_keyboard) {
+  keyboard_replacing_surface_state_ = KeyboardReplacingSurfaceState::kWasShown;
 
   // Clear the autofill state from the username and password element. Note that
   // we don't make use of ClearPreview() here, since this is considering the
-  // elements' SuggestedValue(), which Touch To Fill does not set.
+  // elements' SuggestedValue(), which a keyboard replacing surface does not
+  // set.
   auto focused_input_element =
       autofill_agent_->focused_element().DynamicTo<WebInputElement>();
   if (focused_input_element.IsNull()) {
@@ -1549,10 +1557,11 @@
   if (show_virtual_keyboard) {
     render_frame()->ShowVirtualKeyboard();
 
-    // Since Touch To Fill suppresses the Autofill popup, re-trigger the
-    // suggestions in case the virtual keyboard should be shown. This is limited
-    // to the keyboard accessory, as otherwise it would result in a flickering
-    // of the popup, due to showing the keyboard at the same time.
+    // Since a keyboard replacing surface suppresses the Autofill popup,
+    // re-trigger the suggestions in case the virtual keyboard should be shown.
+    // This is limited to the keyboard accessory, as otherwise it would result
+    // in a flickering of the popup, due to showing the keyboard at the same
+    // time.
     if (IsKeyboardAccessoryEnabled()) {
       ShowSuggestions(focused_input_element, ShowAll(false),
                       GenerationShowing(false));
@@ -1695,7 +1704,8 @@
   last_updated_form_renderer_id_ = FormRendererId();
   field_renderer_id_to_submit_ = FieldRendererId();
 #if BUILDFLAG(IS_ANDROID)
-  touch_to_fill_state_ = TouchToFillState::kShouldShow;
+  keyboard_replacing_surface_state_ =
+      KeyboardReplacingSurfaceState::kShouldShow;
 #endif
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   page_passwords_analyser_.Reset();
diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h
index 57cc9a9..ff12080 100644
--- a/components/autofill/content/renderer/password_autofill_agent.h
+++ b/components/autofill/content/renderer/password_autofill_agent.h
@@ -139,7 +139,7 @@
   void AnnotateFieldsWithParsingResult(
       const ParsingResult& parsing_result) override;
 #if BUILDFLAG(IS_ANDROID)
-  void TouchToFillClosed(bool show_virtual_keyboard) override;
+  void KeyboardReplacingSurfaceClosed(bool show_virtual_keyboard) override;
   void TriggerFormSubmission() override;
 #endif
 
@@ -191,9 +191,9 @@
   // Returns whether the soft keyboard should be suppressed.
   bool ShouldSuppressKeyboard();
 
-  // Asks the agent to show the touch to fill UI for |control_element|. Returns
-  // whether the agent was able to do so.
-  bool TryToShowTouchToFill(
+  // Asks the agent to show the keyboard replacing surface for
+  // |control_element|. Returns whether the agent was able to do so.
+  bool TryToShowKeyboardReplacingSurface(
       const blink::WebFormControlElement& control_element);
 #endif
 
@@ -260,11 +260,13 @@
 
   class DeferringPasswordManagerDriver;
 
-  // Enumeration representing possible Touch To Fill states. This is used to
-  // make sure that Touch To Fill will only be shown in response to the first
-  // password form focus during a frame's life time and to suppress the soft
-  // keyboard when Touch To Fill is shown.
-  enum class TouchToFillState {
+  // Enumeration representing possible keyboard replacing surface states. A
+  // keyboard replacing surface can be either Touch To Fill UI or Android
+  // Credential Manager UI. This is used to make sure that keyboard replacing
+  // surface will only be shown in response to the first password form focus
+  // during a frame's life time and to suppress the soft keyboard when
+  // credential selector sheet is shown.
+  enum class KeyboardReplacingSurfaceState {
     kShouldShow,
     kIsShowing,
     kWasShown,
@@ -569,9 +571,10 @@
   FieldRendererId field_renderer_id_to_submit_;
 
 #if BUILDFLAG(IS_ANDROID)
-  // Current state of Touch To Fill. This is reset during
+  // Current state of the keyboard replacing surface. This is reset during
   // CleanupOnDocumentShutdown.
-  TouchToFillState touch_to_fill_state_ = TouchToFillState::kShouldShow;
+  KeyboardReplacingSurfaceState keyboard_replacing_surface_state_ =
+      KeyboardReplacingSurfaceState::kShouldShow;
 #endif
 };
 
diff --git a/components/autofill/content/renderer/renderer_save_password_progress_logger_unittest.cc b/components/autofill/content/renderer/renderer_save_password_progress_logger_unittest.cc
index bd5037aa..510328d 100644
--- a/components/autofill/content/renderer/renderer_save_password_progress_logger_unittest.cc
+++ b/components/autofill/content/renderer/renderer_save_password_progress_logger_unittest.cc
@@ -62,7 +62,7 @@
                                const gfx::RectF& bounds) override {}
 
 #if BUILDFLAG(IS_ANDROID)
-  void ShowTouchToFill(
+  void ShowKeyboardReplacingSurface(
       autofill::mojom::SubmissionReadinessState submission_readiness) override {
   }
 #endif
diff --git a/components/autofill/core/browser/autofill_experiments_unittest.cc b/components/autofill/core/browser/autofill_experiments_unittest.cc
index 8e3009b..1236887 100644
--- a/components/autofill/core/browser/autofill_experiments_unittest.cc
+++ b/components/autofill/core/browser/autofill_experiments_unittest.cc
@@ -148,9 +148,8 @@
        IsCardUploadEnabled_SyncDoesNotHaveAutofillProfileActiveType) {
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kAutofill));
-  sync_service_.SetFailedDataTypes(syncer::AUTOFILL_PROFILE);
+      /*types=*/{syncer::UserSelectableType::kAutofill});
+  sync_service_.SetFailedDataTypes({syncer::AUTOFILL_PROFILE});
   EXPECT_FALSE(IsCreditCardUploadEnabled(
       AutofillSyncSigninState::kSignedInAndSyncFeatureEnabled));
   histogram_tester.ExpectUniqueSample(
@@ -227,9 +226,8 @@
 
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kAutofill));
-  sync_service_.SetFailedDataTypes(syncer::AUTOFILL_PROFILE);
+      /*types=*/{syncer::UserSelectableType::kAutofill});
+  sync_service_.SetFailedDataTypes({syncer::AUTOFILL_PROFILE});
 
   EXPECT_TRUE(IsCreditCardUploadEnabled(
       AutofillSyncSigninState::kSignedInAndSyncFeatureEnabled));
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component.cc b/components/autofill/core/browser/data_model/autofill_structured_address_component.cc
index e1106ee8..3cdc1813 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component.cc
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component.cc
@@ -326,12 +326,13 @@
     component->FillTreeGaps();
   }
 
-  bool children_has_value = base::ranges::any_of(
-      Subcomponents(), [](const auto* c) { return !c->GetValue().empty(); });
-
-  if (GetValue().empty() && children_has_value &&
+  if (GetValue().empty() &&
       GetVerificationStatus() == VerificationStatus::kNoStatus) {
-    FormatValueFromSubcomponents();
+    std::u16string formatted_value = GetFormattedValueFromSubcomponents();
+    if (!formatted_value.empty() &&
+        IsValueCompatibleWithAncestors(formatted_value)) {
+      SetValue(formatted_value, VerificationStatus::kFormatted);
+    }
   }
 }
 
@@ -504,6 +505,14 @@
   });
 }
 
+bool AddressComponent::IsValueCompatibleWithAncestors(
+    const std::u16string& value) const {
+  bool is_node_compatible =
+      GetValue().empty() || (GetValue().find(value) != std::string::npos);
+  return is_node_compatible &&
+         (!parent_ || parent_->IsValueCompatibleWithAncestors(value));
+}
+
 bool AddressComponent::IsStructureValid() const {
   if (IsAtomic()) {
     return true;
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component.h b/components/autofill/core/browser/data_model/autofill_structured_address_component.h
index 244f778..d2db2fb6 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component.h
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component.h
@@ -528,6 +528,10 @@
   // of the subcomponents. Returns true on success and is allowed to fail.
   bool ParseValueAndAssignSubcomponentsByRegularExpressions();
 
+  // This method verifies that the `value` is compatible with all the node's
+  // anestors.
+  bool IsValueCompatibleWithAncestors(const std::u16string& value) const;
+
   // The unstructured value of this component.
   absl::optional<std::u16string> value_;
 
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc b/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc
index 7848cfc..57db1b2 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/gtest_util.h"
 #include "components/autofill/core/browser/autofill_type.h"
+#include "components/autofill/core/browser/data_model/autofill_structured_address.h"
 #include "components/autofill/core/browser/data_model/autofill_structured_address_name.h"
 #include "components/autofill/core/browser/data_model/autofill_structured_address_test_utils.h"
 #include "components/autofill/core/browser/data_model/autofill_structured_address_utils.h"
@@ -1809,4 +1810,72 @@
   VerifyTestValues(&name, expectation);
 }
 
+TEST(AutofillStructuredAddressAddressComponent,
+     IsValueCompatibleWithAncestorsNonCompatible) {
+  AddressNode address;
+  AddressComponentTestValues test_values = {
+      {.type = ADDRESS_HOME_STREET_ADDRESS,
+       .value = "Flat 42, Floor 7, Tagore Road Hostel, 13, Hitech City Rd",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_FLOOR,
+       .value = "Floor 7",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_APT_NUM,
+       .value = "Flat 42",
+       .status = VerificationStatus::kObserved}};
+
+  AddressComponentTestValues expectation = {
+      {.type = ADDRESS_HOME_STREET_ADDRESS,
+       .value = "Flat 42, Floor 7, Tagore Road Hostel, 13, Hitech City Rd",
+       .status = VerificationStatus::kObserved},
+      // Note that subpremise can not be parsed into "Floor 7 Flat 42" as this
+      // would not be compatible with ADDRESS_HOME_STREET_ADDRESS.
+      {.type = ADDRESS_HOME_SUBPREMISE,
+       .value = "",
+       .status = VerificationStatus::kNoStatus},
+      {.type = ADDRESS_HOME_FLOOR,
+       .value = "Floor 7",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_APT_NUM,
+       .value = "Flat 42",
+       .status = VerificationStatus::kObserved}};
+
+  SetTestValues(&address, test_values);
+  address.CompleteFullTree();
+  VerifyTestValues(&address, expectation);
+}
+
+TEST(AutofillStructuredAddressAddressComponent,
+     IsValueCompatibleWithAncestorsCompatible) {
+  AddressNode address;
+  AddressComponentTestValues test_values = {
+      {.type = ADDRESS_HOME_STREET_ADDRESS,
+       .value = "Floor 7 Flat 42, Tagore Road Hostel, 13, Hitech City Rd",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_FLOOR,
+       .value = "Floor 7",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_APT_NUM,
+       .value = "Flat 42",
+       .status = VerificationStatus::kObserved}};
+
+  AddressComponentTestValues expectation = {
+      {.type = ADDRESS_HOME_STREET_ADDRESS,
+       .value = "Floor 7 Flat 42, Tagore Road Hostel, 13, Hitech City Rd",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_SUBPREMISE,
+       .value = "Floor 7 Flat 42",
+       .status = VerificationStatus::kFormatted},
+      {.type = ADDRESS_HOME_FLOOR,
+       .value = "Floor 7",
+       .status = VerificationStatus::kObserved},
+      {.type = ADDRESS_HOME_APT_NUM,
+       .value = "Flat 42",
+       .status = VerificationStatus::kObserved}};
+
+  SetTestValues(&address, test_values);
+  address.CompleteFullTree();
+  VerifyTestValues(&address, expectation);
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/form_parsing/address_field.cc b/components/autofill/core/browser/form_parsing/address_field.cc
index 1415214..deaaaff8e 100644
--- a/components/autofill/core/browser/form_parsing/address_field.cc
+++ b/components/autofill/core/browser/form_parsing/address_field.cc
@@ -82,6 +82,9 @@
 constexpr MatchParams kStateMatchType =
     kDefaultMatchParamsWith<MatchFieldType::kSelect, MatchFieldType::kSearch>;
 
+constexpr MatchParams kLandmarkMatchType =
+    kDefaultMatchParamsWith<MatchFieldType::kTextArea, MatchFieldType::kSearch>;
+
 }  // namespace
 
 std::unique_ptr<FormField> AddressField::Parse(
@@ -133,8 +136,8 @@
       continue;
     } else if (address_field->ParseAddress(scanner, page_language,
                                            pattern_source) ||
-               address_field->ParseDependentLocalityCityStateCountryZipCode(
-                   scanner, page_language, pattern_source) ||
+               address_field->ParseAddressField(scanner, page_language,
+                                                pattern_source) ||
                address_field->ParseCompany(scanner, page_language,
                                            pattern_source)) {
       has_trailing_non_labeled_fields = false;
@@ -177,7 +180,7 @@
       address_field->state_ || address_field->zip_ || address_field->zip4_ ||
       address_field->street_name_ || address_field->house_number_ ||
       address_field->country_ || address_field->apartment_number_ ||
-      address_field->dependent_locality_) {
+      address_field->dependent_locality_ || address_field->landmark_) {
     // Don't slurp non-labeled fields at the end into the address.
     if (has_trailing_non_labeled_fields)
       scanner->RewindTo(begin_trailing_non_labeled_fields);
@@ -226,6 +229,8 @@
                     kBaseAddressParserScore, field_candidates);
   AddClassification(apartment_number_, ADDRESS_HOME_APT_NUM,
                     kBaseAddressParserScore, field_candidates);
+  AddClassification(landmark_, ADDRESS_HOME_LANDMARK, kBaseAddressParserScore,
+                    field_candidates);
 }
 
 bool AddressField::ParseCompany(AutofillScanner* scanner,
@@ -521,6 +526,23 @@
                              &state_, {log_manager_, "kStateRe"});
 }
 
+bool AddressField::ParseLandmark(AutofillScanner* scanner,
+                                 const LanguageCode& page_language,
+                                 PatternSource pattern_source) {
+  const bool is_enabled_landmark_parsing = base::FeatureList::IsEnabled(
+      features::kAutofillEnableSupportForExtraSettingsVisibleFields);
+  // TODO(crbug.com/1441904) Remove feature check when launched.
+  if (landmark_ || !is_enabled_landmark_parsing) {
+    return false;
+  }
+
+  base::span<const MatchPatternRef> landmark_patterns =
+      GetMatchPatterns("LANDMARK", page_language, pattern_source);
+  return ParseFieldSpecifics(scanner, kLandmarkRe, kLandmarkMatchType,
+                             landmark_patterns, &landmark_,
+                             {log_manager_, "kLandmarkRe"});
+}
+
 AddressField::ParseNameLabelResult AddressField::ParseNameAndLabelSeparately(
     AutofillScanner* scanner,
     const std::u16string& pattern,
@@ -558,17 +580,16 @@
   return RESULT_MATCH_NONE;
 }
 
-bool AddressField::ParseDependentLocalityCityStateCountryZipCode(
-    AutofillScanner* scanner,
-    const LanguageCode& page_language,
-    PatternSource pattern_source) {
+bool AddressField::ParseAddressField(AutofillScanner* scanner,
+                                     const LanguageCode& page_language,
+                                     PatternSource pattern_source) {
   // The |scanner| is not pointing at a field.
   if (scanner->IsEnd())
     return false;
 
   int num_of_missing_types = 0;
   for (const auto* field :
-       {dependent_locality_, city_, state_, country_, zip_}) {
+       {dependent_locality_, city_, state_, country_, zip_, landmark_}) {
     if (!field)
       ++num_of_missing_types;
   }
@@ -589,6 +610,9 @@
       return ParseCountry(scanner, page_language, pattern_source);
     if (!zip_)
       return ParseZipCode(scanner, page_language, pattern_source);
+    if (!landmark_) {
+      return ParseLandmark(scanner, page_language, pattern_source);
+    }
   }
 
   // Check for matches to both the name and the label.
@@ -609,6 +633,11 @@
       ParseNameAndLabelForCountry(scanner, page_language, pattern_source);
   if (country_result == RESULT_MATCH_NAME_LABEL)
     return true;
+  ParseNameLabelResult landmark_result =
+      ParseNameAndLabelForLandmark(scanner, page_language, pattern_source);
+  if (landmark_result == RESULT_MATCH_NAME_LABEL) {
+    return true;
+  }
   ParseNameLabelResult zip_result =
       ParseNameAndLabelForZipCode(scanner, page_language, pattern_source);
   if (zip_result == RESULT_MATCH_NAME_LABEL)
@@ -631,6 +660,9 @@
       return SetFieldAndAdvanceCursor(scanner, &state_);
     if (country_result != RESULT_MATCH_NONE)
       return SetFieldAndAdvanceCursor(scanner, &country_);
+    if (landmark_result != RESULT_MATCH_NONE) {
+      return SetFieldAndAdvanceCursor(scanner, &landmark_);
+    }
     if (zip_result != RESULT_MATCH_NONE)
       return ParseZipCode(scanner, page_language, pattern_source);
   }
@@ -661,6 +693,9 @@
       return SetFieldAndAdvanceCursor(scanner, &state_);
     if (country_result == result)
       return SetFieldAndAdvanceCursor(scanner, &country_);
+    if (landmark_result == result) {
+      return SetFieldAndAdvanceCursor(scanner, &landmark_);
+    }
     if (zip_result == result)
       return ParseZipCode(scanner, page_language, pattern_source);
   }
@@ -789,4 +824,22 @@
       {log_manager_, "kCountryLocationRe"});
 }
 
+AddressField::ParseNameLabelResult AddressField::ParseNameAndLabelForLandmark(
+    AutofillScanner* scanner,
+    const LanguageCode& page_language,
+    PatternSource pattern_source) {
+  const bool is_enabled_landmark_parsing = base::FeatureList::IsEnabled(
+      features::kAutofillEnableSupportForExtraSettingsVisibleFields);
+  // TODO(crbug.com/1441904) Remove feature check when launched.
+  if (landmark_ || !is_enabled_landmark_parsing) {
+    return RESULT_MATCH_NONE;
+  }
+
+  base::span<const MatchPatternRef> landmark_patterns =
+      GetMatchPatterns("LANDMARK", page_language, pattern_source);
+  return ParseNameAndLabelSeparately(scanner, kLandmarkRe, kLandmarkMatchType,
+                                     landmark_patterns, &landmark_,
+                                     {log_manager_, "kLandmarkRe"});
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/form_parsing/address_field.h b/components/autofill/core/browser/form_parsing/address_field.h
index af3e2d9..6a9f2fd9 100644
--- a/components/autofill/core/browser/form_parsing/address_field.h
+++ b/components/autofill/core/browser/form_parsing/address_field.h
@@ -83,13 +83,16 @@
                   const LanguageCode& page_language,
                   PatternSource pattern_source);
 
+  bool ParseLandmark(AutofillScanner* scanner,
+                     const LanguageCode& page_language,
+                     PatternSource pattern_source);
+
   // Parses the current field pointed to by |scanner|, if it exists, and tries
   // to determine if the field's type corresponds to one of the following:
-  // dependent locality, city, state, country, zip, or none of those.
-  bool ParseDependentLocalityCityStateCountryZipCode(
-      AutofillScanner* scanner,
-      const LanguageCode& page_language,
-      PatternSource pattern_source);
+  // dependent locality, city, state, country, zip, landmark or none of those.
+  bool ParseAddressField(AutofillScanner* scanner,
+                         const LanguageCode& page_language,
+                         PatternSource pattern_source);
 
   // Like ParseFieldSpecifics(), but applies |pattern| against the name and
   // label of the current field separately. If the return value is
@@ -127,6 +130,11 @@
       const LanguageCode& page_language,
       PatternSource pattern_source);
 
+  ParseNameLabelResult ParseNameAndLabelForLandmark(
+      AutofillScanner* scanner,
+      const LanguageCode& page_language,
+      PatternSource pattern_source);
+
   ParseNameLabelResult ParseNameAndLabelForState(
       AutofillScanner* scanner,
       const LanguageCode& page_language,
@@ -176,6 +184,9 @@
   // This field is not a raw_ptr<> because it was filtered by the rewriter for:
   // #addr-of
   RAW_PTR_EXCLUSION AutofillField* country_ = nullptr;
+  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+  // #addr-of
+  RAW_PTR_EXCLUSION AutofillField* landmark_ = nullptr;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/form_parsing/address_field_unittest.cc b/components/autofill/core/browser/form_parsing/address_field_unittest.cc
index 68912c55..f2be5ff 100644
--- a/components/autofill/core/browser/form_parsing/address_field_unittest.cc
+++ b/components/autofill/core/browser/form_parsing/address_field_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "components/autofill/core/browser/form_parsing/address_field.h"
+#include "components/autofill/core/browser/field_types.h"
 
 #include <memory>
 
@@ -133,6 +134,17 @@
   ClassifyAndVerify();
 }
 
+// Tests that the landmark is correctly classified.
+TEST_P(AddressFieldTest, ParseLandmark) {
+  // TODO(crbug.com/1441904): Remove once launched.
+  base::test::ScopedFeatureList enabled;
+  enabled.InitAndEnableFeature(
+      features::kAutofillEnableSupportForExtraSettingsVisibleFields);
+
+  AddTextFormFieldData("landmark", "Landmark", ADDRESS_HOME_LANDMARK);
+  ClassifyAndVerify();
+}
+
 TEST_P(AddressFieldTest, ParseCity) {
   AddTextFormFieldData("city", "City", ADDRESS_HOME_CITY);
   ClassifyAndVerify();
@@ -178,8 +190,10 @@
        ParseDependentLocalityCityStateCountryZipcodeTogether) {
   // TODO(crbug.com/1157405): Remove once launched.
   base::test::ScopedFeatureList enabled;
-  enabled.InitAndEnableFeature(
-      features::kAutofillEnableDependentLocalityParsing);
+  enabled.InitWithFeatures(
+      {features::kAutofillEnableDependentLocalityParsing,
+       features::kAutofillEnableSupportForExtraSettingsVisibleFields},
+      {});
 
   AddTextFormFieldData("neighborhood", "Neighborhood",
                        ADDRESS_HOME_DEPENDENT_LOCALITY);
@@ -187,6 +201,7 @@
   AddTextFormFieldData("state", "State", ADDRESS_HOME_STATE);
   AddTextFormFieldData("country", "Country", ADDRESS_HOME_COUNTRY);
   AddTextFormFieldData("zip", "Zip", ADDRESS_HOME_ZIP);
+  AddTextFormFieldData("landmark", "Landmark", ADDRESS_HOME_LANDMARK);
   ClassifyAndVerify();
 }
 
diff --git a/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json b/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json
index d5c4b67..83a4a66 100644
--- a/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json
+++ b/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json
@@ -1224,6 +1224,35 @@
       }
     ]
   },
+  "LANDMARK": {
+    "en": [
+      {
+        "positive_pattern": "landmark",
+        "positive_score": 1.1,
+        "negative_pattern": null,
+        "match_field_attributes": ["LABEL", "NAME"],
+        "match_field_input_types": ["TEXT", "TEXT_AREA", "SEARCH"]
+      }
+    ],
+    "pt": [
+      {
+        "positive_pattern": "(?:ponto|complemento).*referência",
+        "positive_score": 1.1,
+        "negative_pattern": null,
+        "match_field_attributes": ["LABEL", "NAME"],
+        "match_field_input_types": ["TEXT", "TEXT_AREA", "SEARCH"]
+      }
+    ],
+    "es": [
+      {
+        "positive_pattern": "punto.*referencia",
+        "positive_score": 1.1,
+        "negative_pattern": null,
+        "match_field_attributes": ["LABEL", "NAME"],
+        "match_field_input_types": ["TEXT", "TEXT_AREA", "SEARCH"]
+      }
+    ]
+  },
   "SEARCH_TERM": {
     "en": [
       {
diff --git a/components/autofill/core/browser/merchant_promo_code_manager.h b/components/autofill/core/browser/merchant_promo_code_manager.h
index 640773e..ff60317 100644
--- a/components/autofill/core/browser/merchant_promo_code_manager.h
+++ b/components/autofill/core/browser/merchant_promo_code_manager.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_MERCHANT_PROMO_CODE_MANAGER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_MERCHANT_PROMO_CODE_MANAGER_H_
 
+#include "base/gtest_prod_util.h"
 #include "components/autofill/core/browser/autofill_subject.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/single_field_form_filler.h"
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
index 4608afd9..bb20bb6 100644
--- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
+++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "components/autofill/core/browser/autofill_client.h"
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index b18f266..a01cee8 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -1422,8 +1422,7 @@
   sync_service_.SetHasSyncConsent(true);
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kAutofill));
+      /*types=*/{syncer::UserSelectableType::kAutofill});
   EXPECT_EQ(AutofillSyncSigninState::kSignedInAndSyncFeatureEnabled,
             personal_data_->GetSyncSigninState());
   ASSERT_TRUE(TurnOnSyncFeature());
@@ -5376,8 +5375,7 @@
   ASSERT_FALSE(sync_service_.HasSyncConsent());
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kAutofill));
+      /*types=*/{syncer::UserSelectableType::kAutofill});
 
   EXPECT_EQ(AutofillSyncSigninState::kSignedInAndWalletSyncTransportEnabled,
             personal_data_->GetSyncSigninState());
@@ -5441,8 +5439,7 @@
 
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kAutofill));
+      /*types=*/{syncer::UserSelectableType::kAutofill});
   // Make sure there are no opt-ins recorded yet.
   ASSERT_FALSE(prefs::IsUserOptedInWalletSyncTransport(prefs_.get(),
                                                        active_info.account_id));
diff --git a/components/autofill/core/browser/ui/accessory_sheet_enums.h b/components/autofill/core/browser/ui/accessory_sheet_enums.h
index 8c1a0e8..eb4a9ad7 100644
--- a/components/autofill/core/browser/ui/accessory_sheet_enums.h
+++ b/components/autofill/core/browser/ui/accessory_sheet_enums.h
@@ -38,6 +38,7 @@
   GENERATE_PASSWORD_MANUAL = 5,
   TOGGLE_SAVE_PASSWORDS = 6,
   USE_OTHER_PASSWORD = 7,
+  CREDMAN_CONDITIONAL_UI_REENTRY = 8,
   COUNT,
 };
 
diff --git a/components/autofill/core/common/autofill_regex_constants.h b/components/autofill/core/common/autofill_regex_constants.h
index a692ae5..863233c 100644
--- a/components/autofill/core/common/autofill_regex_constants.h
+++ b/components/autofill/core/common/autofill_regex_constants.h
@@ -179,6 +179,10 @@
     u"|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]l(imiz)?|kent)(\\b|_|\\*))"  // tr
     u"|^시[·・]?도"                                                   // ko-KR
     u"|provinci";                                                     // id
+inline constexpr char16_t kLandmarkRe[] =
+    u"landmark"
+    u"|(?:ponto|complemento).*referência"  // pt-BR, pt-PT
+    u"|punto.*referencia";                 // es
 
 /////////////////////////////////////////////////////////////////////////////
 // search_field.cc
diff --git a/components/autofill/core/common/dense_set.h b/components/autofill/core/common/dense_set.h
index 3412d4b..7f3e724 100644
--- a/components/autofill/core/common/dense_set.h
+++ b/components/autofill/core/common/dense_set.h
@@ -6,7 +6,6 @@
 #define COMPONENTS_AUTOFILL_CORE_COMMON_DENSE_SET_H_
 
 #include <array>
-#include <bit>
 #include <climits>
 #include <cstddef>
 #include <iterator>
@@ -17,6 +16,7 @@
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/numerics/safe_conversions.h"
+#include "third_party/abseil-cpp/absl/numeric/bits.h"
 
 namespace autofill {
 
@@ -229,7 +229,7 @@
               0ULL);
     size_t num_set_bits = 0;
     for (const auto word : words_) {
-      num_set_bits += std::popcount(word);
+      num_set_bits += absl::popcount(word);
     }
     return num_set_bits;
   }
diff --git a/components/browser_sync/sync_api_component_factory_impl.cc b/components/browser_sync/sync_api_component_factory_impl.cc
index 48c59d8..e044d90 100644
--- a/components/browser_sync/sync_api_component_factory_impl.cc
+++ b/components/browser_sync/sync_api_component_factory_impl.cc
@@ -32,7 +32,7 @@
 #include "components/history/core/browser/sync/history_model_type_controller.h"
 #include "components/history/core/common/pref_names.h"
 #include "components/password_manager/core/browser/password_store_interface.h"
-#include "components/password_manager/core/browser/sync/password_model_type_controller.h"
+#include "components/password_manager/core/browser/sync/credential_model_type_controller.h"
 #include "components/power_bookmarks/core/power_bookmark_features.h"
 #include "components/power_bookmarks/core/power_bookmark_service.h"
 #include "components/prefs/pref_service.h"
@@ -379,7 +379,8 @@
     if (profile_password_store_) {
       // |profile_password_store_| can be null in tests.
       controllers.push_back(
-          std::make_unique<password_manager::PasswordModelTypeController>(
+          std::make_unique<password_manager::CredentialModelTypeController>(
+              syncer::PASSWORDS,
               profile_password_store_->CreateSyncControllerDelegate(),
               account_password_store_
                   ? account_password_store_->CreateSyncControllerDelegate()
@@ -473,12 +474,15 @@
 #if !BUILDFLAG(IS_ANDROID) || !BUILDFLAG(IS_IOS)
   if (base::FeatureList::IsEnabled(syncer::kSyncWebauthnCredentials) &&
       !disabled_types.Has(syncer::WEBAUTHN_CREDENTIAL)) {
-    controllers.push_back(std::make_unique<ModelTypeController>(
-        syncer::WEBAUTHN_CREDENTIAL,
-        /*delegate_for_full_sync_mode=*/
-        CreateForwardingControllerDelegate(syncer::WEBAUTHN_CREDENTIAL),
-        /*delegate_for_transport_mode=*/
-        CreateForwardingControllerDelegate(syncer::WEBAUTHN_CREDENTIAL)));
+    controllers.push_back(
+        std::make_unique<password_manager::CredentialModelTypeController>(
+            syncer::WEBAUTHN_CREDENTIAL,
+            /*delegate_for_full_sync_mode=*/
+            CreateForwardingControllerDelegate(syncer::WEBAUTHN_CREDENTIAL),
+            /*delegate_for_transport_mode=*/
+            CreateForwardingControllerDelegate(syncer::WEBAUTHN_CREDENTIAL),
+            sync_client_->GetPrefService(), sync_client_->GetIdentityManager(),
+            sync_service));
   }
 #endif
 
diff --git a/components/browser_ui/styles/android/java/res/values/styles.xml b/components/browser_ui/styles/android/java/res/values/styles.xml
index 2ccce996..24ed6b9 100644
--- a/components/browser_ui/styles/android/java/res/values/styles.xml
+++ b/components/browser_ui/styles/android/java/res/values/styles.xml
@@ -126,6 +126,10 @@
         <item name="android:textColor">@color/default_text_color_list</item>
     </style>
 
+    <style name="TextAppearance.Headline2Thick.Primary">
+        <item name="android:textColor">@color/default_text_color_list</item>
+    </style>
+
     <style name="TextAppearance.TextLarge.Primary">
         <item name="android:textColor">@color/default_text_color_list</item>
     </style>
diff --git a/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml b/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml
index 70707c8..234825d 100644
--- a/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml
+++ b/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml
@@ -28,6 +28,7 @@
   <org.chromium.ui.widget.TextViewWithLeading
       android:id="@+id/empty_state_text_title"
       android:maxWidth="@dimen/empty_state_text_width"
+      android:paddingTop="@dimen/empty_state_heading_padding_top"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textAlignment="center"
diff --git a/components/browser_ui/widget/android/java/res/values/dimens.xml b/components/browser_ui/widget/android/java/res/values/dimens.xml
index cb464bc..17e8a9f 100644
--- a/components/browser_ui/widget/android/java/res/values/dimens.xml
+++ b/components/browser_ui/widget/android/java/res/values/dimens.xml
@@ -201,5 +201,6 @@
 
     <!-- Empty view layout dimensions -->
     <dimen name="empty_state_text_width">341dp</dimen>
+    <dimen name="empty_state_heading_padding_top">18dp</dimen>
     <dimen name="empty_state_subheading_padding_top">8dp</dimen>
 </resources>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
index d6bcaa3..0d02d84 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
@@ -35,13 +35,13 @@
         int XR_DELEGATE = 2;
         int SCENE_OVERLAY = 3;
         int START_SURFACE = 4;
-        int SELECTION_POPUP = 5;
-        int MANUAL_FILLING = 6;
-        int FULLSCREEN = 7;
-        int BOTTOM_SHEET = 8;
-        int LOCATION_BAR = 9;
-        int TAB_MODAL_HANDLER = 10;
-        int TAB_SWITCHER = 11;
+        int TAB_SWITCHER = 5;
+        int SELECTION_POPUP = 6;
+        int MANUAL_FILLING = 7;
+        int FULLSCREEN = 8;
+        int BOTTOM_SHEET = 9;
+        int LOCATION_BAR = 10;
+        int TAB_MODAL_HANDLER = 11;
         int CLOSE_WATCHER = 12;
         int FIND_TOOLBAR = 13;
         int TAB_HISTORY = 14;
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java
index 03650e3..1535a627 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java
@@ -265,12 +265,20 @@
         mEmptyViewWrapper = emptyStateView.findViewById(R.id.empty_state_container);
 
         // Set empty state properties.
-        mEmptyImageView.setImageResource(imageResId);
+        setEmptyStateImageRes(imageResId);
         setEmptyStateViewText(emptyHeadingStringResId, emptySubheadingStringResId);
         return mEmptyView;
     }
 
     /**
+     * Sets the empty state view image when the selectable list is empty.
+     * @param imageResId The image view to show when the selectable list is empty.
+     */
+    public void setEmptyStateImageRes(int imageResId) {
+        mEmptyImageView.setImageResource(imageResId);
+    }
+
+    /**
      * Sets the view text when the selectable list is empty.
      * @param emptyStringResId The string to show when the selectable list is empty.
      */
diff --git a/components/browsing_data/content/browsing_data_model.cc b/components/browsing_data/content/browsing_data_model.cc
index b311522..038705c 100644
--- a/components/browsing_data/content/browsing_data_model.cc
+++ b/components/browsing_data/content/browsing_data_model.cc
@@ -4,12 +4,15 @@
 
 #include "components/browsing_data/content/browsing_data_model.h"
 
+#include <set>
+
 #include "base/barrier_closure.h"
 #include "base/containers/enum_set.h"
 #include "base/functional/callback.h"
 #include "base/functional/overloaded.h"
 #include "base/memory/weak_ptr.h"
 #include "components/browsing_data/content/browsing_data_quota_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/browsing_data/core/features.h"
 #include "components/services/storage/public/mojom/storage_usage_info.mojom.h"
 #include "components/services/storage/shared_storage/shared_storage_manager.h"
@@ -17,6 +20,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/storage_partition_config.h"
+#include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "services/network/network_context.h"
 #include "services/network/public/mojom/clear_data_filter.mojom.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
@@ -86,6 +90,7 @@
 
     case BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage:
     case BrowsingDataModel::StorageType::kSharedStorage:
+    case BrowsingDataModel::StorageType::kLocalStorage:
       return data_key.origin().host();
     default:
       NOTREACHED() << "Unexpected StorageType: "
@@ -116,11 +121,13 @@
   explicit StorageRemoverHelper(
       content::StoragePartition* storage_partition,
       scoped_refptr<BrowsingDataQuotaHelper> quota_helper,
+      scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper,
       BrowsingDataModel::Delegate* delegate
       // TODO(crbug.com/1271155): Inject other dependencies.
       )
       : storage_partition_(storage_partition),
         quota_helper_(quota_helper),
+        local_storage_helper_(local_storage_helper),
         delegate_(delegate) {}
 
   void RemoveByPrimaryHost(
@@ -152,6 +159,7 @@
 
   raw_ptr<content::StoragePartition> storage_partition_;
   scoped_refptr<BrowsingDataQuotaHelper> quota_helper_;
+  scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper_;
   raw_ptr<BrowsingDataModel::Delegate, DanglingUntriaged> delegate_;
   base::WeakPtrFactory<StorageRemoverHelper> weak_ptr_factory_{this};
 };
@@ -218,6 +226,11 @@
       helper->quota_helper_->DeleteHostData(storage_key.origin().host(), type);
     }
   }
+
+  if (types.Has(BrowsingDataModel::StorageType::kLocalStorage)) {
+    helper->local_storage_helper_->DeleteStorageKey(
+        storage_key, helper->GetCompleteCallback());
+  }
 }
 
 template <>
@@ -310,7 +323,7 @@
 void OnAttributionReportingLoaded(
     BrowsingDataModel* model,
     base::OnceClosure loaded_callback,
-    std::vector<content::AttributionDataModel::DataKey> attribution_reporting) {
+    std::set<content::AttributionDataModel::DataKey> attribution_reporting) {
   for (const auto& data_key : attribution_reporting) {
     model->AddBrowsingData(
         data_key, BrowsingDataModel::StorageType::kAttributionReporting,
@@ -324,7 +337,7 @@
     base::OnceClosure loaded_callback,
     const std::list<BrowsingDataQuotaHelper::QuotaInfo>& quota_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  for (auto entry : quota_info) {
+  for (const auto& entry : quota_info) {
     model->AddBrowsingData(
         entry.storage_key,
         BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage,
@@ -333,6 +346,18 @@
   std::move(loaded_callback).Run();
 }
 
+void OnLocalStorageLoaded(
+    BrowsingDataModel* model,
+    base::OnceClosure loaded_callback,
+    const std::list<content::StorageUsageInfo>& storage_usage_info) {
+  for (const auto& info : storage_usage_info) {
+    model->AddBrowsingData(info.storage_key,
+                           BrowsingDataModel::StorageType::kLocalStorage,
+                           info.total_size_bytes);
+  }
+  std::move(loaded_callback).Run();
+}
+
 void OnDelegateDataLoaded(
     BrowsingDataModel* model,
     base::OnceClosure loaded_callback,
@@ -508,7 +533,8 @@
 
   // Bind the lifetime of the helper to the lifetime of the callback.
   auto helper = std::make_unique<StorageRemoverHelper>(
-      storage_partition_, quota_helper_, delegate_.get());
+      storage_partition_, quota_helper_, local_storage_helper_,
+      delegate_.get());
   auto* helper_pointer = helper.get();
 
   base::OnceClosure wrapped_completed = base::BindOnce(
@@ -537,25 +563,10 @@
   bool is_cookies_tree_model_deprecated = base::FeatureList::IsEnabled(
       browsing_data::features::kDeprecateCookiesTreeModel);
 
-  // TODO(crbug.com/1271155): Derive this from the StorageTypeSet directly.
-  int storage_backend_count = 2;
-  if (is_shared_storage_enabled) {
-    storage_backend_count++;
-  }
-  if (is_interest_group_enabled) {
-    storage_backend_count++;
-  }
-
-  if (is_attribution_reporting_enabled) {
-    storage_backend_count++;
-  }
-
-  if (is_cookies_tree_model_deprecated) {
-    storage_backend_count++;
-  }
-
   base::RepeatingClosure completion =
-      base::BarrierClosure(storage_backend_count, std::move(finished_callback));
+      base::BindRepeating([](const base::OnceClosure&) {},
+                          mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+                              std::move(finished_callback)));
 
   // The public build interfaces for the model ensure that `this` remains valid
   // until `finished_callback` has been run. Thus, it's safe to pass raw `this`
@@ -586,6 +597,8 @@
   if (is_cookies_tree_model_deprecated) {
     quota_helper_->StartFetching(
         base::BindOnce(&OnQuotaManagedDataLoaded, this, completion));
+    local_storage_helper_->StartFetching(
+        base::BindOnce(&OnLocalStorageLoaded, this, completion));
   }
 
   // Data loaded from non-components storage types via the delegate.
@@ -601,5 +614,8 @@
     : storage_partition_(storage_partition), delegate_(std::move(delegate)) {
   if (storage_partition_) {
     quota_helper_ = BrowsingDataQuotaHelper::Create(storage_partition_);
+    local_storage_helper_ =
+        base::MakeRefCounted<browsing_data::LocalStorageHelper>(
+            storage_partition_);
   }
 }
diff --git a/components/browsing_data/content/browsing_data_model.h b/components/browsing_data/content/browsing_data_model.h
index ac15661..f26befd 100644
--- a/components/browsing_data/content/browsing_data_model.h
+++ b/components/browsing_data/content/browsing_data_model.h
@@ -12,6 +12,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ref.h"
 #include "components/browsing_data/content/browsing_data_quota_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "content/public/browser/attribution_data_model.h"
 #include "content/public/browser/interest_group_manager.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
@@ -42,6 +43,7 @@
   enum class StorageType {
     kTrustTokens = 1,  // Only issuance information considered.
     kSharedStorage = 2,
+    kLocalStorage,
     kInterestGroup,
     kAttributionReporting,
     kPartitionedQuotaStorage,  // Not fetched from disk or deleted.
@@ -262,6 +264,8 @@
 
   // Used to handle quota managed data on IO thread.
   scoped_refptr<BrowsingDataQuotaHelper> quota_helper_;
+  // Used to handle local storage fetch and deletion.
+  scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper_;
 
   // Owning pointer to the delegate responsible for non components/ data
   // retrieval and removal.
diff --git a/components/browsing_data/content/browsing_data_model_unittest.cc b/components/browsing_data/content/browsing_data_model_unittest.cc
index 1cdda69..48e8799 100644
--- a/components/browsing_data/content/browsing_data_model_unittest.cc
+++ b/components/browsing_data/content/browsing_data_model_unittest.cc
@@ -113,13 +113,15 @@
       model(),
       {{kSubdomainOriginHost,
         kSubdomainOrigin,
-        {BrowsingDataModel::StorageType::kTrustTokens, 0, 1}},
+        {{BrowsingDataModel::StorageType::kTrustTokens}, 0, 1}},
        {kSubdomainOriginSite,
         blink::StorageKey::CreateFirstParty(kSubdomainOrigin),
-        {BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 123, 0}},
+        {{BrowsingDataModel::StorageType::kPartitionedQuotaStorage}, 123, 0}},
        {kSubdomainOriginHost,
         blink::StorageKey::CreateFirstParty(kSubdomainOrigin),
-        {BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage, 456, 0}}});
+        {{BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage},
+         456,
+         0}}});
 }
 
 TEST_F(BrowsingDataModelTest, EntryCoalescense) {
@@ -154,11 +156,11 @@
   expected_entries.push_back(
       {kAnotherSiteOriginHost,
        blink::StorageKey::CreateFirstParty(kAnotherSiteOrigin),
-       {BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 345}});
+       {{BrowsingDataModel::StorageType::kPartitionedQuotaStorage}, 345}});
   expected_entries.push_back(
       {kAnotherSiteOriginHost,
        kAnotherSiteOrigin,
-       {BrowsingDataModel::StorageType::kTrustTokens, 456, 6}});
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 456, 6}});
 
   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
                                                              expected_entries);
@@ -184,16 +186,17 @@
   auto expected_entries = std::vector<BrowsingDataEntry>{
       {kSubdomainOriginHost,
        kSubdomainOrigin,
-       {BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}},
       {kAnotherSiteOriginHost,
        kAnotherSiteOrigin,
-       {BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}},
       {kTestOriginHost,
        kTestOrigin,
-       {static_cast<BrowsingDataModel::StorageType>(
+       {{static_cast<BrowsingDataModel::StorageType>(
             browsing_data::TestBrowsingDataModelDelegate::StorageType::
-                kTestDelegateType),
-        0, 0}}};
+                kTestDelegateType)},
+        0,
+        0}}};
 
   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
                                                              expected_entries);
@@ -290,10 +293,11 @@
   auto expected_entries = std::vector<BrowsingDataEntry>{
       {kTestOriginHost,
        kTestOrigin,
-       {static_cast<BrowsingDataModel::StorageType>(
+       {{static_cast<BrowsingDataModel::StorageType>(
             browsing_data::TestBrowsingDataModelDelegate::StorageType::
-                kTestDelegateType),
-        0, 0}}};
+                kTestDelegateType)},
+        0,
+        0}}};
 
   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
                                                              expected_entries);
@@ -361,16 +365,16 @@
   auto expected_entries = std::vector<BrowsingDataEntry>{
       {httpOriginOwned,
        httpOriginOwned,
-       {BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}},
       {httpsOriginOwned,
        httpsOriginOwned,
-       {BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}},
       {"host.owned.com",
        httpHostOwned,
-       {BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}},
       {"host.owned.com",
        httpsHostOwned,
-       {BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
+       {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}},
   };
 
   browsing_data_model_test_util::ValidateBrowsingDataEntries(model.get(),
diff --git a/components/browsing_data/core/browsing_data_policies_utils_unittest.cc b/components/browsing_data/core/browsing_data_policies_utils_unittest.cc
index b71bf73b..4612f7d8 100644
--- a/components/browsing_data/core/browsing_data_policies_utils_unittest.cc
+++ b/components/browsing_data/core/browsing_data_policies_utils_unittest.cc
@@ -41,12 +41,13 @@
   syncer::UserSelectableTypeSet sync_types =
       browsing_data::GetSyncTypesForBrowsingDataLifetime(
           browsing_data_lifetime_value);
-  syncer::UserSelectableTypeSet expected_types = syncer::UserSelectableTypeSet(
+  const syncer::UserSelectableTypeSet expected_types = {
       syncer::UserSelectableType::kAutofill,
       syncer::UserSelectableType::kPreferences,
       syncer::UserSelectableType::kPasswords,
-      syncer::UserSelectableType::kHistory, syncer::UserSelectableType::kTabs,
-      syncer::UserSelectableType::kSavedTabGroups);
+      syncer::UserSelectableType::kHistory,
+      syncer::UserSelectableType::kTabs,
+      syncer::UserSelectableType::kSavedTabGroups};
   EXPECT_EQ(sync_types, expected_types);
 }
 
@@ -74,12 +75,13 @@
   syncer::UserSelectableTypeSet sync_types =
       browsing_data::GetSyncTypesForClearBrowsingData(
           clear_browsing_data_on_exit_value);
-  syncer::UserSelectableTypeSet expected_types = syncer::UserSelectableTypeSet(
+  const syncer::UserSelectableTypeSet expected_types = {
       syncer::UserSelectableType::kAutofill,
       syncer::UserSelectableType::kPreferences,
       syncer::UserSelectableType::kPasswords,
-      syncer::UserSelectableType::kHistory, syncer::UserSelectableType::kTabs,
-      syncer::UserSelectableType::kSavedTabGroups);
+      syncer::UserSelectableType::kHistory,
+      syncer::UserSelectableType::kTabs,
+      syncer::UserSelectableType::kSavedTabGroups};
   EXPECT_EQ(sync_types, expected_types);
 }
 #endif
diff --git a/components/browsing_data/core/history_notice_utils_unittest.cc b/components/browsing_data/core/history_notice_utils_unittest.cc
index c8a1a901..8caf6ef 100644
--- a/components/browsing_data/core/history_notice_utils_unittest.cc
+++ b/components/browsing_data/core/history_notice_utils_unittest.cc
@@ -82,8 +82,7 @@
   // ...or even if there's no custom passphrase, but we're not syncing history.
   sync_service()->GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kPasswords));
+      /*types=*/{syncer::UserSelectableType::kPasswords});
   sync_service()->SetIsUsingExplicitPassphrase(false);
   ExpectShouldPopupDialogAboutOtherFormsOfBrowsingHistoryWithResult(false);
 }
diff --git a/components/browsing_topics/browsing_topics_state.h b/components/browsing_topics/browsing_topics_state.h
index 028e1d3d..55dc1b3 100644
--- a/components/browsing_topics/browsing_topics_state.h
+++ b/components/browsing_topics/browsing_topics_state.h
@@ -7,6 +7,7 @@
 
 #include "base/containers/queue.h"
 #include "base/files/important_file_writer.h"
+#include "base/gtest_prod_util.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "components/browsing_topics/common/common_types.h"
diff --git a/components/cloud_devices/common/cloud_device_description.cc b/components/cloud_devices/common/cloud_device_description.cc
index 68cdcc59..e92c953 100644
--- a/components/cloud_devices/common/cloud_device_description.cc
+++ b/components/cloud_devices/common/cloud_device_description.cc
@@ -63,16 +63,14 @@
   return root_.FindListByDottedPath(path);
 }
 
-base::Value::Dict* CloudDeviceDescription::CreateDictItem(
-    base::StringPiece path) {
-  base::Value* result = root_.SetByDottedPath(path, base::Value::Dict());
-  return result ? &result->GetDict() : nullptr;
+bool CloudDeviceDescription::SetDictItem(base::StringPiece path,
+                                         base::Value::Dict dict) {
+  return root_.SetByDottedPath(path, std::move(dict));
 }
 
-base::Value::List* CloudDeviceDescription::CreateListItem(
-    base::StringPiece path) {
-  base::Value* result = root_.SetByDottedPath(path, base::Value::List());
-  return result ? &result->GetList() : nullptr;
+bool CloudDeviceDescription::SetListItem(base::StringPiece path,
+                                         base::Value::List list) {
+  return root_.SetByDottedPath(path, std::move(list));
 }
 
 }  // namespace cloud_devices
diff --git a/components/cloud_devices/common/cloud_device_description.h b/components/cloud_devices/common/cloud_device_description.h
index 59512566..1f41ec1 100644
--- a/components/cloud_devices/common/cloud_device_description.h
+++ b/components/cloud_devices/common/cloud_device_description.h
@@ -37,10 +37,10 @@
   const base::Value::Dict* GetDictItem(base::StringPiece path) const;
   const base::Value::List* GetListItem(base::StringPiece path) const;
 
-  // Creates item with given type for capability/option.
-  // Returns nullptr if an intermediate Value in the path is not a dictionary.
-  base::Value::Dict* CreateDictItem(base::StringPiece path);
-  base::Value::List* CreateListItem(base::StringPiece path);
+  // Sets item with given type for capability/option. Returns false if an
+  // intermediate Value in the path is not a dictionary.
+  bool SetDictItem(base::StringPiece path, base::Value::Dict dict);
+  bool SetListItem(base::StringPiece path, base::Value::List list);
 
  private:
   base::Value::Dict root_;
diff --git a/components/cloud_devices/common/description_items_inl.h b/components/cloud_devices/common/description_items_inl.h
index 7f6eef3..0411559 100644
--- a/components/cloud_devices/common/description_items_inl.h
+++ b/components/cloud_devices/common/description_items_inl.h
@@ -74,12 +74,13 @@
 void ListCapability<Option, Traits>::SaveTo(
     CloudDeviceDescription* description) const {
   DCHECK(IsValid());
-  base::Value::List* options_list = description->CreateListItem(GetPath());
+  base::Value::List options_list;
   for (const Option& option : options_) {
     base::Value::Dict option_value;
     Traits::Save(option, &option_value);
-    options_list->Append(std::move(option_value));
+    options_list.Append(std::move(option_value));
   }
+  description->SetListItem(GetPath(), std::move(options_list));
 }
 
 template <class Option, class Traits>
@@ -133,9 +134,9 @@
 void SelectionCapability<Option, Traits>::SaveTo(
     CloudDeviceDescription* description) const {
   DCHECK(IsValid());
-  base::Value::Dict* dict =
-      description->CreateDictItem(Traits::GetCapabilityPath());
-  SaveTo(dict);
+  base::Value::Dict dict;
+  SaveTo(&dict);
+  description->SetDictItem(Traits::GetCapabilityPath(), std::move(dict));
 }
 
 template <class Option, class Traits>
@@ -201,10 +202,11 @@
 template <class Traits>
 void BooleanCapability<Traits>::SaveTo(
     CloudDeviceDescription* description) const {
-  base::Value::Dict* dict =
-      description->CreateDictItem(Traits::GetCapabilityPath());
-  if (default_value_ != Traits::kDefault)
-    dict->Set(json::kKeyDefault, default_value_);
+  base::Value::Dict dict;
+  if (default_value_ != Traits::kDefault) {
+    dict.Set(json::kKeyDefault, default_value_);
+  }
+  description->SetDictItem(Traits::GetCapabilityPath(), std::move(dict));
 }
 
 template <class Traits>
@@ -216,7 +218,7 @@
 template <class Traits>
 void EmptyCapability<Traits>::SaveTo(
     CloudDeviceDescription* description) const {
-  description->CreateDictItem(Traits::GetCapabilityPath());
+  description->SetDictItem(Traits::GetCapabilityPath(), base::Value::Dict());
 }
 
 template <class Option, class Traits>
@@ -252,9 +254,9 @@
 void ValueCapability<Option, Traits>::SaveTo(
     CloudDeviceDescription* description) const {
   DCHECK(IsValid());
-  base::Value::Dict* dict =
-      description->CreateDictItem(Traits::GetCapabilityPath());
-  Traits::Save(value(), dict);
+  base::Value::Dict dict;
+  Traits::Save(value(), &dict);
+  description->SetDictItem(Traits::GetCapabilityPath(), std::move(dict));
 }
 
 template <class Option, class Traits>
@@ -290,9 +292,9 @@
 void TicketItem<Option, Traits>::SaveTo(
     CloudDeviceDescription* description) const {
   DCHECK(IsValid());
-  base::Value::Dict* dict =
-      description->CreateDictItem(Traits::GetTicketItemPath());
-  Traits::Save(value(), dict);
+  base::Value::Dict dict;
+  Traits::Save(value(), &dict);
+  description->SetDictItem(Traits::GetTicketItemPath(), std::move(dict));
 }
 
 }  // namespace cloud_devices
diff --git a/components/cloud_devices/common/printer_description_unittest.cc b/components/cloud_devices/common/printer_description_unittest.cc
index 1adea21..6eab788 100644
--- a/components/cloud_devices/common/printer_description_unittest.cc
+++ b/components/cloud_devices/common/printer_description_unittest.cc
@@ -544,8 +544,8 @@
         },
         "vendor_ticket_item": [
           {
-            "id": "label-mode-configured",
-            "value": "cutter"
+            "id": "finishings",
+            "value": "trim"
           }
         ],
         "color": {
@@ -1304,7 +1304,7 @@
   custom_raster.reverse_order_streaming = true;
   custom_raster.rotate_all_pages = false;
   pwg_raster_config.set_value(custom_raster);
-  VendorItem label_cutter("label-mode-configured", "cutter");
+  VendorItem label_cutter("finishings", "trim");
   vendor_items.AddOption(std::move(label_cutter));
   color.set_value(Color(ColorType::STANDARD_MONOCHROME));
   duplex.set_value(DuplexType::NO_DUPLEX);
@@ -1378,8 +1378,8 @@
   EXPECT_TRUE(pwg_raster_config.value().reverse_order_streaming);
   EXPECT_FALSE(pwg_raster_config.value().rotate_all_pages);
   ASSERT_EQ(vendor_items.size(), 1u);
-  EXPECT_EQ(vendor_items[0].id, "label-mode-configured");
-  EXPECT_EQ(vendor_items[0].value, "cutter");
+  EXPECT_EQ(vendor_items[0].id, "finishings");
+  EXPECT_EQ(vendor_items[0].value, "trim");
   EXPECT_EQ(color.value(), Color(ColorType::STANDARD_MONOCHROME));
   EXPECT_EQ(duplex.value(), DuplexType::NO_DUPLEX);
   EXPECT_EQ(orientation.value(), OrientationType::LANDSCAPE);
diff --git a/components/component_updater/component_installer.h b/components/component_updater/component_installer.h
index 56d9094..e8d4960 100644
--- a/components/component_updater/component_installer.h
+++ b/components/component_updater/component_installer.h
@@ -13,6 +13,7 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/sequence_checker.h"
 #include "base/task/task_traits.h"
diff --git a/components/crash/core/app/crashpad.cc b/components/crash/core/app/crashpad.cc
index 573d95a..9ea9021 100644
--- a/components/crash/core/app/crashpad.cc
+++ b/components/crash/core/app/crashpad.cc
@@ -58,7 +58,8 @@
   // This simulates that a CHECK(false) was done at file:line instead of here.
   // This is used instead of base::ImmediateCrash() to give better error
   // messages locally (printed stack for one).
-  logging::CheckError::Check(file, line, "false").stream() << prefix_end;
+  logging::LogMessage check_failure(file, line, logging::LOGGING_FATAL);
+  check_failure.stream() << "Check failed: false. " << prefix_end;
 }
 
 base::FilePath* g_database_path;
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 a9cbf53..3d790e905 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
@@ -701,10 +701,10 @@
                 }
                 try {
                     mCallback.onSucceeded(CronetUrlRequest.this, mResponseInfo);
-                    maybeReportMetrics();
                 } catch (Exception e) {
                     Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucceeded method", e);
                 }
+                maybeReportMetrics();
             }
         };
         postTaskToExecutor(task);
@@ -749,10 +749,10 @@
             public void run() {
                 try {
                     mCallback.onCanceled(CronetUrlRequest.this, mResponseInfo);
-                    maybeReportMetrics();
                 } catch (Exception e) {
                     Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCanceled method", e);
                 }
+                maybeReportMetrics();
             }
         };
         postTaskToExecutor(task);
@@ -823,10 +823,10 @@
             public void run() {
                 try {
                     mCallback.onFailed(CronetUrlRequest.this, mResponseInfo, mException);
-                    maybeReportMetrics();
                 } catch (Exception e) {
                     Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onFailed method", e);
                 }
+                maybeReportMetrics();
             }
         };
         try {
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
index c3ca085..6ebf894 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
@@ -265,6 +265,22 @@
     @Test
     @SmallTest
     @OnlyRunNativeCronet
+    public void testRequestFinishedListenerThrowInTerminalCallback() throws Exception {
+        TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
+        mTestFramework.mCronetEngine.addRequestFinishedListener(requestFinishedListener);
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        callback.setFailure(TestUrlRequestCallback.FailureType.THROW_SYNC,
+                TestUrlRequestCallback.ResponseStep.ON_SUCCEEDED);
+        mTestFramework.mCronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor())
+                .build()
+                .start();
+        callback.blockForDone();
+        requestFinishedListener.blockUntilDone();
+    }
+
+    @Test
+    @SmallTest
+    @OnlyRunNativeCronet
     @SuppressWarnings("deprecation")
     public void testRequestFinishedListenerRemoved() throws Exception {
         TestExecutor testExecutor = new TestExecutor();
diff --git a/components/custom_handlers/protocol_handler_registry_browsertest.cc b/components/custom_handlers/protocol_handler_registry_browsertest.cc
index 63318ba..1f6b678 100644
--- a/components/custom_handlers/protocol_handler_registry_browsertest.cc
+++ b/components/custom_handlers/protocol_handler_registry_browsertest.cc
@@ -171,9 +171,9 @@
 
   // Attempt to add an entry.
   ProtocolHandlerChangeWaiter waiter(registry);
-  ASSERT_TRUE(content::ExecuteScript(fenced_frame_host,
-                                     "navigator.registerProtocolHandler('web+"
-                                     "search', 'test.html?%s', 'test');"));
+  ASSERT_TRUE(content::ExecJs(fenced_frame_host,
+                              "navigator.registerProtocolHandler('web+"
+                              "search', 'test.html?%s', 'test');"));
   waiter.Wait();
 
   // Ensure the registry is still empty.
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 408f1a7..32031f9 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -2083,36 +2083,39 @@
         Intent pickerIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
         pickerIntent.putExtra(Intent.EXTRA_INTENT, intent);
 
-        // Add the fake entry for the embedding app. This behavior is not well documented but works
-        // consistently across Android since L (and at least up to S).
-        PackageManager pm = context.getPackageManager();
-        ArrayList<ShortcutIconResource> icons = new ArrayList<>();
-        ArrayList<String> labels = new ArrayList<>();
-        String packageName = context.getPackageName();
-        String label = "";
-        ShortcutIconResource resource = new ShortcutIconResource();
-        try {
-            ApplicationInfo applicationInfo =
-                    pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
-            label = (String) pm.getApplicationLabel(applicationInfo);
-            Resources resources = pm.getResourcesForApplication(applicationInfo);
-            resource.packageName = packageName;
-            resource.resourceName = resources.getResourceName(applicationInfo.icon);
-            // This will throw a Resources.NotFoundException if the package uses resource
-            // name collapsing/stripping. The ActivityPicker fails to handle this exception, we have
-            // have to check for it here to avoid crashes.
-            resources.getDrawable(resources.getIdentifier(resource.resourceName, null, null), null);
-        } catch (NameNotFoundException | Resources.NotFoundException e) {
-            Log.w(TAG, "No icon resource found for package: " + packageName);
-            // Most likely the app doesn't have an icon and is just a test
-            // app. Android will just use a blank icon.
-            resource.packageName = "";
-            resource.resourceName = "";
+        if (!resolveInfoContainsSelf(resolvingInfos.getIncludingNonDefaultResolveInfos())) {
+            // Add the fake entry for the embedding app. This behavior is not well documented but
+            // works consistently across Android since L (and at least up to S).
+            PackageManager pm = context.getPackageManager();
+            ArrayList<ShortcutIconResource> icons = new ArrayList<>();
+            ArrayList<String> labels = new ArrayList<>();
+            String packageName = context.getPackageName();
+            String label = "";
+            ShortcutIconResource resource = new ShortcutIconResource();
+            try {
+                ApplicationInfo applicationInfo =
+                        pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+                label = (String) pm.getApplicationLabel(applicationInfo);
+                Resources resources = pm.getResourcesForApplication(applicationInfo);
+                resource.packageName = packageName;
+                resource.resourceName = resources.getResourceName(applicationInfo.icon);
+                // This will throw a Resources.NotFoundException if the package uses resource
+                // name collapsing/stripping. The ActivityPicker fails to handle this exception, we
+                // have have to check for it here to avoid crashes.
+                resources.getDrawable(
+                        resources.getIdentifier(resource.resourceName, null, null), null);
+            } catch (NameNotFoundException | Resources.NotFoundException e) {
+                Log.w(TAG, "No icon resource found for package: " + packageName);
+                // Most likely the app doesn't have an icon and is just a test
+                // app. Android will just use a blank icon.
+                resource.packageName = "";
+                resource.resourceName = "";
+            }
+            labels.add(label);
+            icons.add(resource);
+            pickerIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, labels);
+            pickerIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icons);
         }
-        labels.add(label);
-        icons.add(resource);
-        pickerIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, labels);
-        pickerIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icons);
 
         // Call startActivityForResult on the PICK_ACTIVITY intent, which will set the component of
         // the data result to the component of the chosen app.
@@ -2132,6 +2135,11 @@
                         // Quirk of how we use the ActivityChooser - if the embedding app is
                         // chosen we get an intent back with ACTION_CREATE_SHORTCUT.
                         if (data.getAction().equals(Intent.ACTION_CREATE_SHORTCUT)) {
+                            // Ensure we don't loop asking the user to choose an app, then
+                            // re-asking when we navigate to the same URL.
+                            params.getRedirectHandler()
+                                    .setShouldNotOverrideUrlLoadingOnCurrentRedirectChain();
+
                             // It's pretty arbitrary whether to prefer the data URL or the fallback
                             // URL here. We could consider preferring the fallback URL, as the URL
                             // was probably intending to leave Chrome, but loading the URL the site
diff --git a/components/feed/core/proto/BUILD.gn b/components/feed/core/proto/BUILD.gn
index 45b8a786..2cd2d85 100644
--- a/components/feed/core/proto/BUILD.gn
+++ b/components/feed/core/proto/BUILD.gn
@@ -25,6 +25,7 @@
     "v2/wire/chrome_fulfillment_info.proto",
     "v2/wire/client_info.proto",
     "v2/wire/client_user_profiles.proto",
+    "v2/wire/column_types.proto",
     "v2/wire/consistency_token.proto",
     "v2/wire/content_id.proto",
     "v2/wire/content_lifetime.proto",
@@ -43,6 +44,7 @@
     "v2/wire/feed_request.proto",
     "v2/wire/feed_response.proto",
     "v2/wire/info_card.proto",
+    "v2/wire/language_preferences.proto",
     "v2/wire/next_page_token.proto",
     "v2/wire/payload_metadata.proto",
     "v2/wire/reliability_logging_enums.proto",
@@ -52,11 +54,13 @@
     "v2/wire/response.proto",
     "v2/wire/server_experiment_data.proto",
     "v2/wire/stream_structure.proto",
+    "v2/wire/table.proto",
     "v2/wire/there_and_back_again_data.proto",
     "v2/wire/token.proto",
     "v2/wire/upload_actions_request.proto",
     "v2/wire/upload_actions_response.proto",
     "v2/wire/version.proto",
+    "v2/wire/view_demotion_profile_extension.proto",
     "v2/wire/web_feed_id.proto",
     "v2/wire/web_feed_identifier_token.proto",
     "v2/wire/web_feed_matcher.proto",
diff --git a/components/feed/core/proto/v2/store.proto b/components/feed/core/proto/v2/store.proto
index 7da4132..f73dc0c1 100644
--- a/components/feed/core/proto/v2/store.proto
+++ b/components/feed/core/proto/v2/store.proto
@@ -30,6 +30,7 @@
 // recommendedIndex                 -> recommended_web_feed_index
 // R/<web_feed_id>                  -> recommended_web_feed
 // W/<operation-id>                 -> pending_web_feed_operation
+// v/<docid>/<timestamp>            -> docview
 message Record {
   oneof data {
     StreamData stream_data = 1;
@@ -42,6 +43,7 @@
     WebFeedInfo recommended_web_feed = 8;
     RecommendedWebFeedIndex recommended_web_feed_index = 9;
     PendingWebFeedOperation pending_web_feed_operation = 10;
+    DocView doc_view = 11;
   }
 }
 
@@ -310,3 +312,10 @@
   // Reason for this change.
   feedwire.webfeed.WebFeedChangeReason change_reason = 5;
 }
+
+message DocView {
+  // Unique ID for the content that was viewed.
+  uint64 docid = 1;
+  // Unix timestamp when the view occurred.
+  int64 view_time_millis = 4;
+}
diff --git a/components/feed/core/proto/v2/wire/capabilities_debug_data.proto b/components/feed/core/proto/v2/wire/capabilities_debug_data.proto
index e68399e..bff8bb1 100644
--- a/components/feed/core/proto/v2/wire/capabilities_debug_data.proto
+++ b/components/feed/core/proto/v2/wire/capabilities_debug_data.proto
@@ -11,6 +11,6 @@
 option optimize_for = LITE_RUNTIME;
 
 message CapabilitiesDebugData {
-  repeated Capability client_specified_capabilities = 1;
-  repeated Capability server_fulfilled_capabilities = 2;
+  repeated Capability client_specified_capabilities = 1 [packed = true];
+  repeated Capability server_fulfilled_capabilities = 2 [packed = true];
 }
diff --git a/components/feed/core/proto/v2/wire/capability.proto b/components/feed/core/proto/v2/wire/capability.proto
index 7f262ba..71ace20 100644
--- a/components/feed/core/proto/v2/wire/capability.proto
+++ b/components/feed/core/proto/v2/wire/capability.proto
@@ -38,7 +38,6 @@
   ON_DEVICE_USER_PROFILE = 70;
   INVALIDATE_CACHE_COMMAND = 73;
   INFO_CARD_ACKNOWLEDGEMENT_TRACKING = 77;
-  THANK_CREATOR = 79;
   OPEN_IN_NEW_TAB_IN_GROUP = 81;
   SYNTHETIC_CAPABILITIES = 89;
   OPEN_WEB_FEED_COMMAND = 98;
diff --git a/components/feed/core/proto/v2/wire/client_info.proto b/components/feed/core/proto/v2/wire/client_info.proto
index 06856a1..cacd34fb 100644
--- a/components/feed/core/proto/v2/wire/client_info.proto
+++ b/components/feed/core/proto/v2/wire/client_info.proto
@@ -9,6 +9,7 @@
 import "components/feed/core/proto/v2/wire/chrome_client_info.proto";
 import "components/feed/core/proto/v2/wire/device.proto";
 import "components/feed/core/proto/v2/wire/display_info.proto";
+import "components/feed/core/proto/v2/wire/language_preferences.proto";
 import "components/feed/core/proto/v2/wire/version.proto";
 
 option optimize_for = LITE_RUNTIME;
@@ -18,6 +19,8 @@
     UNKNOWN_PLATFORM = 0;
     ANDROID_ID = 1;
     IOS = 2;
+    MOBILE_WEB = 3;
+    DESKTOP_WEB = 4;
   }
   enum AppType {
     CHROME_ANDROID = 3;
@@ -31,5 +34,6 @@
   optional string client_instance_id = 7;
   optional string advertising_id = 8;
   optional Device device = 10;
+  optional LanguagePreferences language_preferences = 15;
   optional ChromeClientInfo chrome_client_info = 338478298;
 }
diff --git a/components/feed/core/proto/v2/wire/client_user_profiles.proto b/components/feed/core/proto/v2/wire/client_user_profiles.proto
index 36f9c154..0f2cfbf 100644
--- a/components/feed/core/proto/v2/wire/client_user_profiles.proto
+++ b/components/feed/core/proto/v2/wire/client_user_profiles.proto
@@ -6,6 +6,8 @@
 
 package feedwire;
 
+import "components/feed/core/proto/v2/wire/view_demotion_profile_extension.proto";
+
 option optimize_for = LITE_RUNTIME;
 
 message ClientUserProfiles {
@@ -39,4 +41,6 @@
   repeated ContentMediaXEntityActionCounts content_media_x_entity = 2;
   repeated CardCategoryXEntityActionCounts card_category_x_entity = 3;
 }
-message ViewDemotionProfile {}
+message ViewDemotionProfile {
+  optional ViewDemotionProfileExtension view_demotion_profile = 1000;
+}
diff --git a/components/feed/core/proto/v2/wire/column_types.proto b/components/feed/core/proto/v2/wire/column_types.proto
new file mode 100644
index 0000000..c09e4d5f
--- /dev/null
+++ b/components/feed/core/proto/v2/wire/column_types.proto
@@ -0,0 +1,14 @@
+// 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.
+
+syntax = "proto2";
+
+package feedwire;
+
+option optimize_for = LITE_RUNTIME;
+
+enum TypeKind {
+  TYPE_UNKNOWN = 0;
+  TYPE_UINT64 = 4;
+}
diff --git a/components/feed/core/proto/v2/wire/feed_entry_point_source.proto b/components/feed/core/proto/v2/wire/feed_entry_point_source.proto
index 6da0de8..3d44657 100644
--- a/components/feed/core/proto/v2/wire/feed_entry_point_source.proto
+++ b/components/feed/core/proto/v2/wire/feed_entry_point_source.proto
@@ -14,6 +14,6 @@
   CHROME_SINGLE_WEB_FEED_MENU = 24;
   CHROME_SINGLE_WEB_FEED_ATTRIBUTION = 25;
   CHROME_SINGLE_WEB_FEED_RECOMMENDATION = 26;
-  CHROME_SINGLE_WEB_FEED_GROUP_HEADER = 28;
   CHROME_SINGLE_WEB_FEED_OTHER = 27;
+  CHROME_SINGLE_WEB_FEED_GROUP_HEADER = 28;
 }
diff --git a/components/feed/core/proto/v2/wire/info_card.proto b/components/feed/core/proto/v2/wire/info_card.proto
index 51f9a2d..6f2fa34 100644
--- a/components/feed/core/proto/v2/wire/info_card.proto
+++ b/components/feed/core/proto/v2/wire/info_card.proto
@@ -24,6 +24,6 @@
   optional int64 last_view_timestamp = 6;
 }
 message InfoCardServingInfo {
-  repeated int32 fulfilled_info_card_types = 1;
-  repeated int64 known_info_card_types = 2;
+  repeated int32 fulfilled_info_card_types = 1 [packed = true];
+  repeated int64 known_info_card_types = 2 [packed = true];
 }
diff --git a/components/feed/core/proto/v2/wire/language_preferences.proto b/components/feed/core/proto/v2/wire/language_preferences.proto
new file mode 100644
index 0000000..935068b
--- /dev/null
+++ b/components/feed/core/proto/v2/wire/language_preferences.proto
@@ -0,0 +1,13 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+package feedwire;
+
+option optimize_for = LITE_RUNTIME;
+
+message LanguagePreferences {
+  optional string discover_preferred_language = 1;
+}
diff --git a/components/feed/core/proto/v2/wire/reliability_logging_enums.proto b/components/feed/core/proto/v2/wire/reliability_logging_enums.proto
index d1c699a..5949deb 100644
--- a/components/feed/core/proto/v2/wire/reliability_logging_enums.proto
+++ b/components/feed/core/proto/v2/wire/reliability_logging_enums.proto
@@ -60,10 +60,3 @@
   FULL_FEED_ERROR = 3;
   INTERNAL_ERROR = 4;
 }
-enum DiscoverImagesInViewportLoadResult {
-  IMAGES_UNSPECIFIED = 0 [deprecated = true];
-  IMAGES_FULLY_RENDERED = 1;
-  SOME_IMAGES_FAILED_TO_RENDER = 2;
-  ALL_IMAGES_FAILED_TO_RENDER = 3;
-  IMAGE_LOADING_CANCELLED = 4;
-}
diff --git a/components/feed/core/proto/v2/wire/server_experiment_data.proto b/components/feed/core/proto/v2/wire/server_experiment_data.proto
index e7752c0..6e8c2906 100644
--- a/components/feed/core/proto/v2/wire/server_experiment_data.proto
+++ b/components/feed/core/proto/v2/wire/server_experiment_data.proto
@@ -11,7 +11,7 @@
 message ServerExperimentData {
   message NamespacedExperimentInfo {
     optional string namespace = 1;
-    repeated int32 experiment_id = 2;
+    repeated int32 experiment_id = 2 [packed = true];
   }
   repeated NamespacedExperimentInfo namespaced_experiment_info = 1;
 }
diff --git a/components/feed/core/proto/v2/wire/table.proto b/components/feed/core/proto/v2/wire/table.proto
new file mode 100644
index 0000000..d567ef97
--- /dev/null
+++ b/components/feed/core/proto/v2/wire/table.proto
@@ -0,0 +1,22 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+package feedwire;
+
+import "components/feed/core/proto/v2/wire/column_types.proto";
+
+option optimize_for = LITE_RUNTIME;
+
+message Table {
+  message Column {
+    optional TypeKind type = 1;
+    optional string name = 3;
+    repeated uint64 uint64_values = 4 [packed = true];
+  }
+  optional string name = 5;
+  optional int32 num_rows = 4;
+  repeated Column columns = 3;
+}
diff --git a/components/feed/core/proto/v2/wire/view_demotion_profile_extension.proto b/components/feed/core/proto/v2/wire/view_demotion_profile_extension.proto
new file mode 100644
index 0000000..60f0868
--- /dev/null
+++ b/components/feed/core/proto/v2/wire/view_demotion_profile_extension.proto
@@ -0,0 +1,15 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+package feedwire;
+
+import "components/feed/core/proto/v2/wire/table.proto";
+
+option optimize_for = LITE_RUNTIME;
+
+message ViewDemotionProfileExtension {
+  repeated Table tables = 1;
+}
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index 8d372c1..3085e49 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -114,6 +114,8 @@
     "types.h",
     "user_actions_collector.cc",
     "user_actions_collector.h",
+    "view_demotion.cc",
+    "view_demotion.h",
     "web_feed_subscription_coordinator.cc",
     "web_feed_subscription_coordinator.h",
     "web_feed_subscriptions/fetch_recommended_web_feeds_task.cc",
@@ -245,6 +247,7 @@
     "test/test_util.h",
     "types_unittest.cc",
     "user_actions_collector_unittest.cc",
+    "view_demotion_unittest.cc",
     "web_feed_subscriptions/web_feed_index_unittest.cc",
     "xsurface_datastore_unittest.cc",
   ]
diff --git a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
index 2cd1eeb..70f0100 100644
--- a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
+++ b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
@@ -4051,6 +4051,120 @@
   EXPECT_FALSE(on_clear_all.called());
 }
 
+class SignedOutViewDemotionTest : public FeedApiTest {
+ public:
+  void SetUp() override {
+    std::vector<base::test::FeatureRef> enabled_features =
+                                            {kFeedSignedOutViewDemotion},
+                                        disabled_features = {};
+    features.InitWithFeatures(enabled_features, disabled_features);
+    FeedApiTest::SetUp();
+  }
+
+  base::test::ScopedFeatureList features;
+};
+
+TEST_F(SignedOutViewDemotionTest, ViewsAreSent) {
+  account_info_ = {};
+  // Simulate loading the feed, viewing one document, and closing Chrome.
+  {
+    response_translator_.InjectResponse(MakeTypicalInitialModelState());
+    TestForYouSurface surface(stream_.get());
+    WaitForIdleTaskQueue();
+
+    stream_->RecordContentViewed(123);
+    WaitForIdleTaskQueue();
+  }
+
+  // Simulate loading the feed again later after restart, triggering a refresh.
+  task_environment_.FastForwardBy(GetFeedConfig().stale_content_threshold +
+                                  base::Minutes(1));
+  CreateStream(true);
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestForYouSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  EXPECT_THAT(network_.query_request_sent->feed_request()
+                  .client_user_profiles()
+                  .view_demotion_profile(),
+              EqualsTextProto(
+                  R"({
+  view_demotion_profile {
+    tables {
+      name: "url_all_ondevice"
+      num_rows: 1
+      columns {
+        type: 4
+        name: "dimension_key"
+        uint64_values: 123
+      }
+      columns {
+        type: 4
+        name: "FEED_CARD_VIEW"
+        uint64_values: 1
+      }
+    }
+  }
+})"));
+}
+
+TEST_F(SignedOutViewDemotionTest, ViewsAreNotStoredWhenSignedIn) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestForYouSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  stream_->RecordContentViewed(123);
+  WaitForIdleTaskQueue();
+
+  CallbackReceiver<std::vector<feedstore::DocView>> read_callback;
+  store_->ReadDocViews(read_callback.Bind());
+  EXPECT_THAT(read_callback.RunAndGetResult(), testing::IsEmpty());
+}
+
+TEST_F(SignedOutViewDemotionTest, ViewsAreNotStoredWhenFeatureIsOff) {
+  base::test::ScopedFeatureList features;
+  std::vector<base::test::FeatureRef> enabled_features = {},
+                                      disabled_features = {
+                                          kFeedSignedOutViewDemotion};
+  features.InitWithFeatures(enabled_features, disabled_features);
+
+  account_info_ = {};
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestForYouSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  stream_->RecordContentViewed(123);
+  WaitForIdleTaskQueue();
+
+  CallbackReceiver<std::vector<feedstore::DocView>> read_callback;
+  store_->ReadDocViews(read_callback.Bind());
+  EXPECT_THAT(read_callback.RunAndGetResult(), testing::IsEmpty());
+}
+
+TEST_F(SignedOutViewDemotionTest, OldViewsAreDeleted) {
+  account_info_ = {};
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestForYouSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  network_.query_request_sent.reset();
+  stream_->RecordContentViewed(123);
+
+  task_environment_.FastForwardBy(base::Hours(72) + base::Minutes(1));
+
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  stream_->ManualRefresh(surface.GetStreamType(), base::DoNothing());
+  WaitForIdleTaskQueue();
+
+  EXPECT_THAT(network_.query_request_sent->feed_request()
+                  .client_user_profiles()
+                  .view_demotion_profile(),
+              EqualsProto(feedwire::ViewDemotionProfile()));
+  CallbackReceiver<std::vector<feedstore::DocView>> read_callback;
+  store_->ReadDocViews(read_callback.Bind());
+  EXPECT_THAT(read_callback.RunAndGetResult(), testing::IsEmpty());
+}
+
 // Keep instantiations at the bottom.
 INSTANTIATE_TEST_SUITE_P(FeedApiTest,
                          FeedStreamTestForAllStreamTypes,
diff --git a/components/feed/core/v2/config.h b/components/feed/core/v2/config.h
index 451d3f3..8fe0bf1 100644
--- a/components/feed/core/v2/config.h
+++ b/components/feed/core/v2/config.h
@@ -65,6 +65,9 @@
   int max_prefetch_image_requests_per_refresh = 50;
   // Maximum size of most recent viewed content hash list.
   int max_most_recent_viewed_content_hashes = 100;
+  // Maximum number of docviews to send in a request for signed-out view
+  // demotion.
+  size_t max_docviews_to_send = 500;
 
   // Configuration for Web Feeds.
 
diff --git a/components/feed/core/v2/feed_store.cc b/components/feed/core/v2/feed_store.cc
index a2871a6..37616a1 100644
--- a/components/feed/core/v2/feed_store.cc
+++ b/components/feed/core/v2/feed_store.cc
@@ -28,16 +28,17 @@
 
 // Keys are defined as:
 // [Key format]                     -> [Record field]
-// S/<stream-key>                    -> stream_data
-// T/<stream-key>/<sequence-number>  -> stream_structures
-// c/<stream-key>/<content-id>       -> content
-// s/<stream-key>/<content-id>       -> shared_state
+// S/<stream-key>                   -> stream_data
+// T/<stream-key>/<sequence-number> -> stream_structures
+// c/<stream-key>/<content-id>      -> content
+// s/<stream-key>/<content-id>      -> shared_state
 // a/<action-id>                    -> action
 // m                                -> metadata
 // subs                             -> subscribed_web_feeds
 // recommendedIndex                 -> recommended_web_feed_index
 // R/<web_feed_id>                  -> recommended_web_feed
 // W/<operation-id>                 -> pending_web_feed_operation
+// v/<docid>/<timestamp>            -> docview
 constexpr char kLocalActionPrefix[] = "a/";
 constexpr char kMetadataKey[] = "m";
 constexpr char kSubscribedFeedsKey[] = "subs";
@@ -84,10 +85,13 @@
 std::string LocalActionKey(int64_t id) {
   return kLocalActionPrefix + base::NumberToString(id);
 }
-
 std::string LocalActionKey(const LocalActionId& id) {
   return LocalActionKey(id.GetUnsafeValue());
 }
+std::string DocViewKey(const feedstore::DocView& doc_view) {
+  return base::StrCat({"v/", base::NumberToString(doc_view.docid()), "/",
+                       base::NumberToString(doc_view.view_time_millis())});
+}
 
 // Returns true if the record key is for stream data (stream_data,
 // stream_structures, content, shared_state).
@@ -188,6 +192,8 @@
       return base::StrCat(
           {"W/",
            base::NumberToString(record.pending_web_feed_operation().id())});
+    case feedstore::Record::kDocView:
+      return DocViewKey(record.doc_view());
     case feedstore::Record::DATA_NOT_SET:
       break;
   }
@@ -262,6 +268,12 @@
   return record;
 }
 
+feedstore::Record MakeRecord(feedstore::DocView doc_view) {
+  feedstore::Record record;
+  *record.mutable_doc_view() = std::move(doc_view);
+  return record;
+}
+
 template <typename T>
 std::pair<std::string, feedstore::Record> MakeKeyAndRecord(T record_data) {
   std::pair<std::string, feedstore::Record> result;
@@ -895,4 +907,41 @@
       std::move(keys_to_remove), base::DoNothing());
 }
 
+void FeedStore::WriteDocView(feedstore::DocView doc_view) {
+  std::vector<feedstore::Record> records;
+  records.push_back(MakeRecord(std::move(doc_view)));
+  Write(std::move(records), base::DoNothing());
+}
+
+void FeedStore::RemoveDocViews(std::vector<feedstore::DocView> doc_views) {
+  if (doc_views.empty()) {
+    return;
+  }
+  auto keys_to_remove = std::make_unique<std::vector<std::string>>();
+  for (const feedstore::DocView& doc_view : doc_views) {
+    keys_to_remove->push_back(DocViewKey(doc_view));
+  }
+  database_->UpdateEntries(
+      /*entries_to_save=*/std::make_unique<
+          std::vector<std::pair<std::string, feedstore::Record>>>(),
+      std::move(keys_to_remove), base::DoNothing());
+}
+
+void FeedStore::ReadDocViews(
+    base::OnceCallback<void(std::vector<feedstore::DocView>)> callback) {
+  auto adapter =
+      [](base::OnceCallback<void(std::vector<feedstore::DocView>)> callback,
+         bool ok,
+         std::unique_ptr<std::map<std::string, feedstore::Record>> results) {
+        std::vector<feedstore::DocView> doc_views;
+        for (auto& entry : *results) {
+          feedstore::Record& record = entry.second;
+          doc_views.push_back(std::move(record.doc_view()));
+        }
+        std::move(callback).Run(std::move(doc_views));
+      };
+  database_->LoadKeysAndEntriesInRange(
+      "v/0", "v/~", base::BindOnce(adapter, std::move(callback)));
+}
+
 }  // namespace feed
diff --git a/components/feed/core/v2/feed_store.h b/components/feed/core/v2/feed_store.h
index 4222018..7a5cf8d 100644
--- a/components/feed/core/v2/feed_store.h
+++ b/components/feed/core/v2/feed_store.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "base/containers/flat_set.h"
-#include "base/functional/callback_forward.h"
+#include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "components/feed/core/proto/v2/store.pb.h"
@@ -151,6 +151,11 @@
   void WritePendingWebFeedOperation(
       feedstore::PendingWebFeedOperation operation);
 
+  void WriteDocView(feedstore::DocView doc_view);
+  void RemoveDocViews(std::vector<feedstore::DocView> doc_ids);
+  void ReadDocViews(
+      base::OnceCallback<void(std::vector<feedstore::DocView>)> callback);
+
   bool IsInitializedForTesting() const;
 
   leveldb_proto::ProtoDatabase<feedstore::Record>* GetDatabaseForTesting() {
diff --git a/components/feed/core/v2/feed_store_unittest.cc b/components/feed/core/v2/feed_store_unittest.cc
index 3a6dd37..956811e 100644
--- a/components/feed/core/v2/feed_store_unittest.cc
+++ b/components/feed/core/v2/feed_store_unittest.cc
@@ -24,10 +24,13 @@
 #include "components/feed/core/v2/test/test_util.h"
 #include "components/feed/feed_feature_list.h"
 #include "components/leveldb_proto/testing/fake_db.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace feed {
 namespace {
+using testing::ElementsAre;
+using testing::Pair;
 
 using LoadStreamResult = FeedStore::LoadStreamResult;
 
@@ -62,6 +65,13 @@
   return action;
 }
 
+feedstore::DocView CreateDocView(uint64_t docid, int64_t view_time_millis) {
+  feedstore::DocView view;
+  view.set_docid(docid);
+  view.set_view_time_millis(view_time_millis);
+  return view;
+}
+
 }  // namespace
 
 class FeedStoreTest : public testing::Test {
@@ -950,4 +960,87 @@
   EXPECT_EQ("", StoreToString());
 }
 
+TEST_F(FeedStoreTest, WriteDocView) {
+  MakeFeedStore({});
+  feedstore::DocView dv = CreateDocView(10, 11);
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+
+  EXPECT_EQ(R"([v/10/11] {
+  doc_view {
+    docid: 10
+    view_time_millis: 11
+  }
+}
+)",
+            StoreToString());
+}
+
+TEST_F(FeedStoreTest, RemoveDocViewsNotExist) {
+  MakeFeedStore({});
+  feedstore::DocView dv = CreateDocView(10, 11);
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+
+  // docid doesn't match
+  store_->RemoveDocViews({CreateDocView(11, 11)});
+  fake_db_->UpdateCallback(true);
+
+  EXPECT_EQ(R"([v/10/11] {
+  doc_view {
+    docid: 10
+    view_time_millis: 11
+  }
+}
+)",
+            StoreToString());
+}
+
+TEST_F(FeedStoreTest, RemoveDocViewsDoesExist) {
+  MakeFeedStore({});
+  feedstore::DocView dv = CreateDocView(10, 9000);
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+  dv.set_docid(11);
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+  dv.set_docid(12);
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+
+  store_->RemoveDocViews({CreateDocView(10, 9000), CreateDocView(12, 9000)});
+  fake_db_->UpdateCallback(true);
+  ASSERT_THAT(db_entries_, ElementsAre(Pair("v/11/9000", EqualsTextProto(R"({
+  doc_view {
+    docid: 11
+    view_time_millis: 9000
+  }
+})"))));
+}
+
+TEST_F(FeedStoreTest, ReadDocViews) {
+  MakeFeedStore({});
+  feedstore::DocView dv;
+  dv.set_docid(0);
+  dv.set_view_time_millis(11);
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+  dv.set_docid(std::numeric_limits<uint64_t>::max());
+  store_->WriteDocView(dv);
+  fake_db_->UpdateCallback(true);
+
+  CallbackReceiver<std::vector<feedstore::DocView>> result;
+  store_->ReadDocViews(result.Bind());
+  fake_db_->LoadCallback(true);
+
+  ASSERT_TRUE(result.GetResult());
+  ASSERT_THAT(*result.GetResult(), ElementsAre(EqualsTextProto(R"({
+  view_time_millis: 11
+})"),
+                                               EqualsTextProto(R"({
+  docid: 18446744073709551615
+  view_time_millis: 11
+})")));
+}
+
 }  // namespace feed
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index eed53ac..4671489 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -738,6 +738,12 @@
   if (stream_kind != StreamKind::kUnknown)
     SetStreamStale(StreamType(stream_kind), true);
 }
+void FeedStream::RecordContentViewed(uint64_t docid) {
+  if (!store_) {
+    return;
+  }
+  WriteDocViewIfEnabled(*this, docid);
+}
 
 DebugStreamData FeedStream::GetDebugStreamData() {
   return ::feed::prefs::GetDebugStreamData(*profile_prefs_);
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index f6ff7fc..131a7fc 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -150,6 +150,7 @@
                          const LoggingParameters& logging_parameters) override;
   bool WasUrlRecentlyNavigatedFromFeed(const GURL& url) override;
   void InvalidateContentCacheFor(StreamKind stream_kind) override;
+  void RecordContentViewed(uint64_t docid) override;
   DebugStreamData GetDebugStreamData() override;
   void ForceRefreshForDebugging(const StreamType& stream_type) override;
   std::string DumpStateForDebugging() override;
diff --git a/components/feed/core/v2/feedstore_util.cc b/components/feed/core/v2/feedstore_util.cc
index f44b622f..80223ea 100644
--- a/components/feed/core/v2/feedstore_util.cc
+++ b/components/feed/core/v2/feedstore_util.cc
@@ -232,6 +232,13 @@
   return md;
 }
 
+feedstore::DocView CreateDocView(uint64_t docid, base::Time timestamp) {
+  feedstore::DocView doc_view;
+  doc_view.set_docid(docid);
+  doc_view.set_view_time_millis(feedstore::ToTimestampMillis(timestamp));
+  return doc_view;
+}
+
 absl::optional<Metadata> SetStreamViewContentHashes(
     const Metadata& metadata,
     const StreamType& stream_type,
diff --git a/components/feed/core/v2/feedstore_util.h b/components/feed/core/v2/feedstore_util.h
index c3f69c5..3cc7a74 100644
--- a/components/feed/core/v2/feedstore_util.h
+++ b/components/feed/core/v2/feedstore_util.h
@@ -57,6 +57,8 @@
                       const feed::StreamType& stream_type,
                       const base::Time& fetch_time);
 feedstore::Metadata MakeMetadata(const std::string& gaia);
+feedstore::DocView CreateDocView(uint64_t docid,
+                                 base::Time timestamp = base::Time::Now());
 
 // Mutations of Metadata. Metadata will need stored again after being changed,
 // call `FeedStream::SetMetadata()`.
diff --git a/components/feed/core/v2/proto_util.cc b/components/feed/core/v2/proto_util.cc
index 01c81a91..365b95f 100644
--- a/components/feed/core/v2/proto_util.cc
+++ b/components/feed/core/v2/proto_util.cc
@@ -20,6 +20,7 @@
 #include "components/feed/core/proto/v2/wire/feed_query.pb.h"
 #include "components/feed/core/proto/v2/wire/feed_request.pb.h"
 #include "components/feed/core/proto/v2/wire/request.pb.h"
+#include "components/feed/core/proto/v2/wire/table.pb.h"
 #include "components/feed/core/proto/v2/wire/web_feed_id.pb.h"
 #include "components/feed/core/proto/v2/wire/web_feed_identifier_token.pb.h"
 #include "components/feed/core/v2/config.h"
@@ -164,15 +165,9 @@
 
   feed_request.add_client_capability(Capability::READ_LATER);
 
-#if BUILDFLAG(IS_ANDROID)
-  // Note that the Crow feature is referenced as THANK_CREATOR within the feed.
-  if (base::FeatureList::IsEnabled(kShareCrowButton)) {
-    feed_request.add_client_capability(Capability::THANK_CREATOR);
-  }
   if (base::FeatureList::IsEnabled(kCormorant)) {
     feed_request.add_client_capability(Capability::OPEN_WEB_FEED_COMMAND);
   }
-#endif
 
   if (base::FeatureList::IsEnabled(kPersonalizeFeedUnsignedUsers)) {
     feed_request.add_client_capability(Capability::ON_DEVICE_USER_PROFILE);
@@ -313,6 +308,24 @@
       ->mutable_sign_in_status()
       ->set_sign_in_status(request_metadata.sign_in_status);
 }
+
+void WriteDocIdsTable(const std::vector<DocViewCount> doc_view_counts,
+                      feedwire::Table& table) {
+  table.set_name("url_all_ondevice");
+  table.set_num_rows(doc_view_counts.size());
+
+  feedwire::Table::Column* ids = table.add_columns();
+  ids->set_name("dimension_key");
+  ids->set_type(feedwire::TypeKind::TYPE_UINT64);
+  feedwire::Table::Column* counts = table.add_columns();
+  counts->set_name("FEED_CARD_VIEW");
+  counts->set_type(feedwire::TypeKind::TYPE_UINT64);
+  for (const auto& doc_view_count : doc_view_counts) {
+    ids->add_uint64_values(doc_view_count.docid);
+    counts->add_uint64_values(doc_view_count.view_count);
+  }
+}
+
 }  // namespace
 
 std::string ContentIdString(const feedwire::ContentId& content_id) {
@@ -395,7 +408,8 @@
     feedwire::FeedQuery::RequestReason request_reason,
     const RequestMetadata& request_metadata,
     const std::string& consistency_token,
-    const SingleWebFeedEntryPoint single_feed_entry_point) {
+    const SingleWebFeedEntryPoint single_feed_entry_point,
+    const std::vector<DocViewCount> doc_view_counts) {
   feedwire::Request request = CreateFeedQueryRequest(
       stream_type, request_reason, request_metadata, consistency_token,
       std::string(), single_feed_entry_point);
@@ -424,6 +438,15 @@
   SetInfoCardTrackingStates(&request, request_metadata);
   SetTimesFollowedFromWebPageMenu(&request, request_metadata);
   SetChromeSignInStatus(&request, request_metadata);
+
+  if (!doc_view_counts.empty()) {
+    WriteDocIdsTable(doc_view_counts, *request.mutable_feed_request()
+                                           ->mutable_client_user_profiles()
+                                           ->mutable_view_demotion_profile()
+                                           ->mutable_view_demotion_profile()
+                                           ->add_tables());
+  }
+
   return request;
 }
 
diff --git a/components/feed/core/v2/proto_util.h b/components/feed/core/v2/proto_util.h
index 042e5316..0d287c15 100644
--- a/components/feed/core/v2/proto_util.h
+++ b/components/feed/core/v2/proto_util.h
@@ -12,6 +12,7 @@
 #include "components/feed/core/proto/v2/wire/feed_query.pb.h"
 #include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/types.h"
+#include "components/feed/core/v2/view_demotion.h"
 
 namespace feedwire {
 class Request;
@@ -54,7 +55,8 @@
     feedwire::FeedQuery::RequestReason request_reason,
     const RequestMetadata& request_metadata,
     const std::string& consistency_token,
-    const SingleWebFeedEntryPoint single_feed_entry_point);
+    const SingleWebFeedEntryPoint single_feed_entry_point,
+    const std::vector<DocViewCount> doc_view_counts);
 
 feedwire::Request CreateFeedQueryLoadMoreRequest(
     const RequestMetadata& request_metadata,
diff --git a/components/feed/core/v2/proto_util_unittest.cc b/components/feed/core/v2/proto_util_unittest.cc
index d27ab44..bbe6547 100644
--- a/components/feed/core/v2/proto_util_unittest.cc
+++ b/components/feed/core/v2/proto_util_unittest.cc
@@ -71,7 +71,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   // Additional features may be present based on the current testing config.
@@ -100,7 +101,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -118,7 +120,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   // Additional features may be present based on the current testing config.
@@ -135,7 +138,8 @@
   feedwire::Request request = CreateFeedQueryRefreshRequest(
       StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
       request_metadata,
-      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther);
+      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+      /*doc_view_counts=*/{});
 
   EXPECT_TRUE(request.feed_request()
                   .feed_query()
@@ -149,7 +153,8 @@
   feedwire::Request request = CreateFeedQueryRefreshRequest(
       StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
       request_metadata,
-      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther);
+      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+      /*doc_view_counts=*/{});
 
   EXPECT_FALSE(request.feed_request()
                    .feed_query()
@@ -171,7 +176,8 @@
   feedwire::Request request = CreateFeedQueryRefreshRequest(
       StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
       request_metadata,
-      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther);
+      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+      /*doc_view_counts=*/{});
 
   ASSERT_EQ(2, request.feed_request()
                    .feed_query()
@@ -195,7 +201,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           request_metadata,
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -211,7 +218,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -231,7 +239,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -242,23 +251,6 @@
 
 #endif
 
-#if BUILDFLAG(IS_ANDROID)
-TEST(ProtoUtilTest, CrowButtonEnabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({kShareCrowButton}, {});
-  feedwire::FeedRequest request =
-      CreateFeedQueryRefreshRequest(
-          StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
-          /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
-          .feed_request();
-
-  ASSERT_THAT(request.client_capability(),
-              Contains(feedwire::Capability::THANK_CREATOR));
-}
-#endif  // BUILDFLAG(IS_ANDROID)
-
-#if BUILDFLAG(IS_ANDROID)
 TEST(ProtoUtilTest, CormorantEnabled) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures({kCormorant}, {});
@@ -267,7 +259,8 @@
           StreamType(StreamKind::kSingleWebFeed, "test_web_id"),
           feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kMenu)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kMenu,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -277,7 +270,6 @@
                 .feed_entry_point_source_value(),
             feedwire::FeedEntryPointSource::CHROME_SINGLE_WEB_FEED_MENU);
 }
-#endif  // BUILDFLAG(IS_ANDROID)
 
 TEST(ProtoUtilTest, InfoCardAcknowledgementTrackingDisabled) {
   base::test::ScopedFeatureList scoped_feature_list;
@@ -286,7 +278,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(
@@ -302,7 +295,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           request_metadata,
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -319,7 +313,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           request_metadata,
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -335,7 +330,8 @@
       CreateFeedQueryRefreshRequest(
           StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
           /*request_metadata=*/{},
-          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther)
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
           .feed_request();
 
   ASSERT_THAT(request.client_capability(),
@@ -349,7 +345,8 @@
   feedwire::Request request = CreateFeedQueryRefreshRequest(
       StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
       request_metadata,
-      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther);
+      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+      /*doc_view_counts=*/{});
 
   feedwire::ChromeSignInStatus::SignInStatus status =
       request.feed_request()
@@ -360,5 +357,53 @@
   ASSERT_EQ(status, request_metadata.sign_in_status);
 }
 
+TEST(ProtoUtilTest, WithoutDocIds) {
+  feedwire::FeedRequest request =
+      CreateFeedQueryRefreshRequest(
+          StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
+          /*request_metadata=*/{},
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{})
+          .feed_request();
+
+  ASSERT_THAT(
+      request.client_user_profiles().has_discover_user_actions_profile(),
+      testing::IsFalse());
+}
+
+TEST(ProtoUtilTest, WithDocIds) {
+  feedwire::FeedRequest request =
+      CreateFeedQueryRefreshRequest(
+          StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
+          /*request_metadata=*/{},
+          /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+          /*doc_view_counts=*/{DocViewCount{123, 1}, DocViewCount{456, 2}})
+          .feed_request();
+
+  EXPECT_STRINGS_EQUAL(
+      R"({
+  view_demotion_profile {
+    tables {
+      name: "url_all_ondevice"
+      num_rows: 2
+      columns {
+        type: 4
+        name: "dimension_key"
+        uint64_values: 123
+        uint64_values: 456
+      }
+      columns {
+        type: 4
+        name: "FEED_CARD_VIEW"
+        uint64_values: 1
+        uint64_values: 2
+      }
+    }
+  }
+}
+)",
+      ToTextProto(request.client_user_profiles().view_demotion_profile()));
+}
+
 }  // namespace
 }  // namespace feed
diff --git a/components/feed/core/v2/public/feed_api.h b/components/feed/core/v2/public/feed_api.h
index 375461a..32d901b3 100644
--- a/components/feed/core/v2/public/feed_api.h
+++ b/components/feed/core/v2/public/feed_api.h
@@ -154,6 +154,11 @@
   // shown/loaded.
   virtual void InvalidateContentCacheFor(StreamKind stream_kind) = 0;
 
+  // Called when content is viewed, providing a unique identifier of the content
+  // which was viewed. Used for signed-out view demotion. This may be called
+  // regardless of sign-in status, but may be ignored.
+  virtual void RecordContentViewed(uint64_t docid) = 0;
+
   // User interaction reporting. Unless otherwise documented, these have no
   // side-effects other than reporting metrics.
 
diff --git a/components/feed/core/v2/public/ntp_feed_content_fetcher.cc b/components/feed/core/v2/public/ntp_feed_content_fetcher.cc
index 13502fa..4f747ec9 100644
--- a/components/feed/core/v2/public/ntp_feed_content_fetcher.cc
+++ b/components/feed/core/v2/public/ntp_feed_content_fetcher.cc
@@ -85,7 +85,8 @@
   feedwire::Request request = CreateFeedQueryRefreshRequest(
       StreamType(StreamKind::kFollowing),
       feedwire::FeedQuery::INTERACTIVE_WEB_FEED, RequestMetadata(),
-      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther);
+      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+      /*doc_view_counts=*/{});
 
   feedwire::ClientInfo* client_info =
       request.mutable_feed_request()->mutable_client_info();
@@ -126,4 +127,4 @@
   return false;
 }
 
-}  // namespace feed
\ No newline at end of file
+}  // namespace feed
diff --git a/components/feed/core/v2/public/test/stub_feed_api.h b/components/feed/core/v2/public/test/stub_feed_api.h
index 102961c..fb1cd22 100644
--- a/components/feed/core/v2/public/test/stub_feed_api.h
+++ b/components/feed/core/v2/public/test/stub_feed_api.h
@@ -72,6 +72,7 @@
   }
   bool WasUrlRecentlyNavigatedFromFeed(const GURL& url) override;
   void InvalidateContentCacheFor(StreamKind stream_kind) override {}
+  void RecordContentViewed(uint64_t docid) override {}
   void ReportSliceViewed(SurfaceId surface_id,
                          const StreamType& stream_type,
                          const std::string& slice_id) override {}
diff --git a/components/feed/core/v2/tasks/load_stream_task.cc b/components/feed/core/v2/tasks/load_stream_task.cc
index b00fb85..c963a50 100644
--- a/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_task.cc
@@ -31,6 +31,7 @@
 #include "components/feed/core/v2/stream_model.h"
 #include "components/feed/core/v2/tasks/upload_actions_task.h"
 #include "components/feed/core/v2/types.h"
+#include "components/feed/core/v2/view_demotion.h"
 #include "components/feed/feed_feature_list.h"
 #include "net/base/net_errors.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -197,8 +198,9 @@
     GetLaunchReliabilityLogger().LogCacheReadStart();
 
   if (options_.load_type == LoadType::kManualRefresh) {
-    // Pass empty pending actions to force reading from the store.
-    UploadActions(std::vector<feedstore::StoredAction>());
+    std::vector<feedstore::StoredAction> empty_pending_actions;
+    LoadFromNetwork1(std::move(empty_pending_actions),
+                     /*need_to_read_pending_actions=*/true);
     return;
   }
 
@@ -246,19 +248,40 @@
     stale_store_state_ = std::move(result.update_request);
   }
 
+  LoadFromNetwork1(std::move(result.pending_actions),
+                   /*need_to_read_pending_actions=*/false);
+}
+
+void LoadStreamTask::LoadFromNetwork1(
+    std::vector<feedstore::StoredAction> pending_actions_from_store,
+    bool need_to_read_pending_actions) {
   // Don't consume quota if refreshed by user.
   LaunchResult should_make_request = stream_->ShouldMakeFeedQueryRequest(
       options_.stream_type, options_.load_type,
       /*consume_quota=*/options_.load_type != LoadType::kManualRefresh);
-  if (should_make_request.load_stream_status != LoadStreamStatus::kNoStatus)
+  if (should_make_request.load_stream_status != LoadStreamStatus::kNoStatus) {
     return Done(should_make_request);
+  }
+
+  ReadDocViewDigestIfEnabled(
+      *stream_, base::BindOnce(&LoadStreamTask::LoadFromNetwork2, GetWeakPtr(),
+                               std::move(pending_actions_from_store),
+                               need_to_read_pending_actions));
+}
+
+void LoadStreamTask::LoadFromNetwork2(
+    std::vector<feedstore::StoredAction> pending_actions_from_store,
+    bool need_to_read_pending_actions,
+    DocViewDigest doc_view_digest) {
+  stream_->GetStore().RemoveDocViews(doc_view_digest.old_doc_views);
+  doc_view_counts_ = std::move(doc_view_digest.doc_view_counts);
 
   // If no pending action exists in the store, go directly to send query
   // request.
-  if (result.pending_actions.empty()) {
+  if (pending_actions_from_store.empty()) {
     SendFeedQueryRequest();
   } else {
-    UploadActions(std::move(result.pending_actions));
+    UploadActions(std::move(pending_actions_from_store));
   }
 }
 
@@ -299,7 +322,7 @@
       options_.stream_type,
       GetRequestReason(options_.stream_type, options_.load_type),
       request_metadata, stream_->GetMetadata().consistency_token(),
-      options_.single_feed_entry_point);
+      options_.single_feed_entry_point, doc_view_counts_);
 
   const AccountInfo account_info = stream_->GetAccountInfo();
   stream_->GetMetricsReporter().NetworkRefreshRequestStarted(
diff --git a/components/feed/core/v2/tasks/load_stream_task.h b/components/feed/core/v2/tasks/load_stream_task.h
index 01d1c9f..ddc0abe 100644
--- a/components/feed/core/v2/tasks/load_stream_task.h
+++ b/components/feed/core/v2/tasks/load_stream_task.h
@@ -24,6 +24,7 @@
 #include "components/feed/core/v2/tasks/load_stream_from_store_task.h"
 #include "components/feed/core/v2/tasks/upload_actions_task.h"
 #include "components/feed/core/v2/types.h"
+#include "components/feed/core/v2/view_demotion.h"
 #include "components/offline_pages/task/task.h"
 #include "components/version_info/channel.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -31,6 +32,7 @@
 namespace feed {
 class FeedStream;
 class LaunchReliabilityLogger;
+struct DocViewDigest;
 
 // Loads the stream model from storage or network. If data is refreshed from the
 // network, it is persisted to |FeedStore| by overwriting any existing stream
@@ -119,6 +121,13 @@
       std::vector<feedstore::StoredAction> pending_actions_from_store);
   void SendFeedQueryRequest();
 
+  void LoadFromNetwork1(
+      std::vector<feedstore::StoredAction> pending_actions_from_store,
+      bool need_to_read_pending_actions);
+  void LoadFromNetwork2(
+      std::vector<feedstore::StoredAction> pending_actions_from_store,
+      bool need_to_read_pending_actions,
+      DocViewDigest doc_view_digest);
   void LoadFromStoreComplete(LoadStreamFromStoreTask::Result result);
   void UploadActionsComplete(UploadActionsTask::Result result);
   void QueryApiRequestComplete(
@@ -136,6 +145,8 @@
   std::unique_ptr<LoadStreamFromStoreTask> load_from_store_task_;
   std::unique_ptr<StreamModelUpdateRequest> stale_store_state_;
 
+  std::vector<DocViewCount> doc_view_counts_;
+
   // Information to be stuffed in |Result|.
   LoadStreamStatus load_from_store_status_ = LoadStreamStatus::kNoStatus;
   absl::optional<NetworkResponseInfo> network_response_info_;
diff --git a/components/feed/core/v2/test/proto_printer.cc b/components/feed/core/v2/test/proto_printer.cc
index 4063f3d..ee5a844 100644
--- a/components/feed/core/v2/test/proto_printer.cc
+++ b/components/feed/core/v2/test/proto_printer.cc
@@ -10,6 +10,7 @@
 #include "components/feed/core/proto/v2/wire/action_diagnostic_info.pb.h"
 #include "components/feed/core/proto/v2/wire/action_payload.pb.h"
 #include "components/feed/core/proto/v2/wire/client_info.pb.h"
+#include "components/feed/core/proto/v2/wire/client_user_profiles.pb.h"
 #include "components/feed/core/proto/v2/wire/consistency_token.pb.h"
 #include "components/feed/core/proto/v2/wire/content_id.pb.h"
 #include "components/feed/core/proto/v2/wire/feed_action.pb.h"
@@ -186,6 +187,7 @@
     PRINT_ONEOF(content);
     PRINT_ONEOF(local_action);
     PRINT_ONEOF(shared_state);
+    PRINT_ONEOF(doc_view);
     EndMessage();
     return *this;
   }
@@ -344,6 +346,13 @@
     EndMessage();
     return *this;
   }
+  TextProtoPrinter& operator<<(const feedstore::DocView& v) {
+    BeginMessage();
+    PRINT_FIELD(docid);
+    PRINT_FIELD(view_time_millis);
+    EndMessage();
+    return *this;
+  }
   TextProtoPrinter& operator<<(const feedui::StreamUpdate& v) {
     BeginMessage();
     PRINT_FIELD(updated_slices);
@@ -435,6 +444,43 @@
     return *this;
   }
   TextProtoPrinter& operator<<(
+      const feedwire::ViewDemotionProfileExtension& v) {
+    BeginMessage();
+    PRINT_FIELD(tables);
+    EndMessage();
+    return *this;
+  }
+  TextProtoPrinter& operator<<(const feedwire::Table& v) {
+    BeginMessage();
+    PRINT_FIELD(name);
+    PRINT_FIELD(num_rows);
+    PRINT_FIELD(columns);
+    EndMessage();
+    return *this;
+  }
+  TextProtoPrinter& operator<<(const feedwire::Table::Column& v) {
+    BeginMessage();
+    PRINT_FIELD(type);
+    PRINT_FIELD(name);
+    PRINT_FIELD(uint64_values);
+    EndMessage();
+    return *this;
+  }
+  TextProtoPrinter& operator<<(const feedwire::ClientUserProfiles& v) {
+    BeginMessage();
+    PRINT_FIELD(view_demotion_profile);
+    EndMessage();
+    return *this;
+  }
+
+  TextProtoPrinter& operator<<(const feedwire::ViewDemotionProfile& v) {
+    BeginMessage();
+    PRINT_FIELD(view_demotion_profile);
+    EndMessage();
+    return *this;
+  }
+
+  TextProtoPrinter& operator<<(
       const feedwire::webfeed::ListRecommendedWebFeedsRequest& v) {
     BeginMessage();
     EndMessage();
@@ -547,6 +593,7 @@
 DECLARE_PRINTER(feedstore, SubscribedWebFeeds)
 DECLARE_PRINTER(feedstore, WebFeedInfo)
 DECLARE_PRINTER(feedstore, PendingWebFeedOperation)
+DECLARE_PRINTER(feedstore, DocView)
 DECLARE_PRINTER(feedui, StreamUpdate)
 DECLARE_PRINTER(feedwire, ActionPayload)
 DECLARE_PRINTER(feedwire, ClientInfo)
@@ -555,7 +602,12 @@
 DECLARE_PRINTER(feedwire, InfoCardTrackingState)
 DECLARE_PRINTER(feedwire, UploadActionsRequest)
 DECLARE_PRINTER(feedwire, UploadActionsResponse)
+DECLARE_PRINTER(feedwire, ViewDemotionProfileExtension)
+DECLARE_PRINTER(feedwire, ViewDemotionProfile)
+DECLARE_PRINTER(feedwire, Table)
+DECLARE_PRINTER(feedwire, Table::Column)
 DECLARE_PRINTER(feedwire, Version)
+DECLARE_PRINTER(feedwire, ClientUserProfiles)
 DECLARE_PRINTER(feedwire::webfeed, Image)
 DECLARE_PRINTER(feedwire::webfeed, ListRecommendedWebFeedsRequest)
 DECLARE_PRINTER(feedwire::webfeed, ListRecommendedWebFeedsResponse)
diff --git a/components/feed/core/v2/test/proto_printer.h b/components/feed/core/v2/test/proto_printer.h
index 1c9da74c..ffbe784a 100644
--- a/components/feed/core/v2/test/proto_printer.h
+++ b/components/feed/core/v2/test/proto_printer.h
@@ -10,6 +10,7 @@
 
 #include "components/feed/core/proto/v2/store.pb.h"
 #include "components/feed/core/proto/v2/ui.pb.h"
+#include "components/feed/core/proto/v2/wire/client_user_profiles.pb.h"
 #include "components/feed/core/proto/v2/wire/info_card.pb.h"
 #include "components/feed/core/proto/v2/wire/upload_actions_request.pb.h"
 #include "components/feed/core/proto/v2/wire/upload_actions_response.pb.h"
@@ -52,6 +53,7 @@
 DECLARE_PRINTER(feedstore, SubscribedWebFeeds)
 DECLARE_PRINTER(feedstore, WebFeedInfo)
 DECLARE_PRINTER(feedstore, PendingWebFeedOperation)
+DECLARE_PRINTER(feedstore, DocView)
 DECLARE_PRINTER(feedui, StreamUpdate)
 DECLARE_PRINTER(feedwire, ActionPayload)
 DECLARE_PRINTER(feedwire, ClientInfo)
@@ -62,6 +64,11 @@
 DECLARE_PRINTER(feedwire, InfoCardTrackingState)
 DECLARE_PRINTER(feedwire, UploadActionsRequest)
 DECLARE_PRINTER(feedwire, UploadActionsResponse)
+DECLARE_PRINTER(feedwire, ViewDemotionProfileExtension)
+DECLARE_PRINTER(feedwire, ViewDemotionProfile)
+DECLARE_PRINTER(feedwire, Table)
+DECLARE_PRINTER(feedwire, Table::Column)
+DECLARE_PRINTER(feedwire, ClientUserProfiles)
 DECLARE_PRINTER(feedwire::webfeed, ListRecommendedWebFeedsRequest)
 DECLARE_PRINTER(feedwire::webfeed, ListRecommendedWebFeedsResponse)
 DECLARE_PRINTER(feedwire::webfeed, ListWebFeedsRequest)
diff --git a/components/feed/core/v2/test/test_util.cc b/components/feed/core/v2/test/test_util.cc
index b66043c..a2fd996 100644
--- a/components/feed/core/v2/test/test_util.cc
+++ b/components/feed/core/v2/test/test_util.cc
@@ -5,12 +5,20 @@
 #include "components/feed/core/v2/test/test_util.h"
 #include "base/logging.h"
 #include "base/run_loop.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/test/bind.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace feed {
 
+std::string TrimLines(base::StringPiece text) {
+  auto lines = base::SplitString(text, "\n", base::TRIM_WHITESPACE,
+                                 base::SPLIT_WANT_NONEMPTY);
+  return base::JoinString(lines, "\n");
+}
 void RunLoopUntil(base::RepeatingCallback<bool()> criteria,
                   const std::string& failure_message) {
   RunLoopUntil(criteria,
diff --git a/components/feed/core/v2/test/test_util.h b/components/feed/core/v2/test/test_util.h
index 6ea3506..e730e8e 100644
--- a/components/feed/core/v2/test/test_util.h
+++ b/components/feed/core/v2/test/test_util.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/functional/callback.h"
+#include "base/strings/string_piece.h"
 #include "base/time/time.h"
 #include "components/feed/core/v2/test/proto_printer.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -37,6 +38,22 @@
                                << got___;                   \
   }
 
+// Trims whitespace from begin and end of all lines of text.
+std::string TrimLines(base::StringPiece text);
+
+// Does the protobuf argument's ToTextProto() output match message? Allows some
+// whitespace differences.
+MATCHER_P(EqualsTextProto, message, message) {
+  std::string actual_string = ToTextProto(arg);
+  if (TrimLines(actual_string) != TrimLines(message)) {
+    return testing::ExplainMatchResult(testing::Eq(message), ToTextProto(arg),
+                                       result_listener);
+  } else {
+    return true;
+  }
+}
+
+// Does the protobuf argument match message?
 MATCHER_P(EqualsProto, message, ToTextProto(message)) {
   std::string expected_serialized, actual_serialized;
   message.SerializeToString(&expected_serialized);
diff --git a/components/feed/core/v2/view_demotion.cc b/components/feed/core/v2/view_demotion.cc
new file mode 100644
index 0000000..faf5f5b5
--- /dev/null
+++ b/components/feed/core/v2/view_demotion.cc
@@ -0,0 +1,154 @@
+// 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 "components/feed/core/v2/view_demotion.h"
+
+#include <algorithm>
+#include <map>
+#include <ostream>
+#include <tuple>
+#include <vector>
+
+#include "base/check_op.h"
+#include "base/containers/flat_set.h"
+#include "base/feature_list.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/ranges/algorithm.h"
+#include "base/time/time.h"
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/v2/config.h"
+#include "components/feed/core/v2/feed_store.h"
+#include "components/feed/core/v2/feed_stream.h"
+#include "components/feed/core/v2/feedstore_util.h"
+#include "components/feed/feed_feature_list.h"
+
+namespace feed {
+namespace {
+base::TimeDelta kMaxViewAge = base::Hours(72);
+
+bool IsEnabled(FeedStream& feed_stream) {
+  return base::FeatureList::IsEnabled(kFeedSignedOutViewDemotion) &&
+         !feed_stream.IsSignedIn();
+}
+}  // namespace
+
+namespace internal {
+DocViewDigest CreateDigest(std::vector<feedstore::DocView> all_views) {
+  const base::Time now = base::Time::Now();
+  const base::Time too_old = now - kMaxViewAge;
+  DocViewDigest digest;
+
+  // Remove all views that are too old.
+  base::EraseIf(all_views, [&](const feedstore::DocView& view) {
+    base::Time view_time =
+        feedstore::FromTimestampMillis(view.view_time_millis());
+    if (view_time > now || view_time < too_old) {
+      digest.old_doc_views.push_back(view);
+      return true;
+    }
+    return false;
+  });
+
+  // Note that all_views will be sorted by the datastore key, which is prefixed
+  // with the docid, and therefore we can assume that all views for a given
+  // docid are adjacent in this list. We take advantage of this here.
+  for (const feedstore::DocView& view : all_views) {
+    if (digest.doc_view_counts.empty() ||
+        digest.doc_view_counts.back().docid != view.docid()) {
+      digest.doc_view_counts.push_back(DocViewCount{view.docid(), 1});
+    } else {
+      ++digest.doc_view_counts.back().view_count;
+    }
+  }
+
+  // If there are too many recent entries, remove docids which have been viewed
+  // least recently.
+  if (digest.doc_view_counts.size() > GetFeedConfig().max_docviews_to_send) {
+    // Get the most recent view time for each doc, sort by time and select the
+    // docids to remove.
+    size_t remove_count =
+        digest.doc_view_counts.size() - GetFeedConfig().max_docviews_to_send;
+    std::vector<std::pair<int64_t, uint64_t>> latest_view_to_docid;
+    for (const feedstore::DocView& view : all_views) {
+      if (latest_view_to_docid.empty() ||
+          latest_view_to_docid.back().second != view.docid()) {
+        latest_view_to_docid.emplace_back(view.view_time_millis(),
+                                          view.docid());
+      } else {
+        auto& view_time = latest_view_to_docid.back().first;
+        view_time = std::max(view_time, view.view_time_millis());
+      }
+    }
+
+    base::ranges::sort(latest_view_to_docid);
+
+    std::vector<uint64_t> docids_to_remove;
+    docids_to_remove.reserve(remove_count);
+    for (auto iter = latest_view_to_docid.begin();
+         iter != latest_view_to_docid.begin() + remove_count; ++iter) {
+      docids_to_remove.push_back(iter->second);
+    }
+
+    // Finally, remove the old docids, and add to old_doc_views.
+    base::flat_set<uint64_t> docids_to_remove_set(std::move(docids_to_remove));
+    base::EraseIf(digest.doc_view_counts, [&](const DocViewCount& view_count) {
+      return docids_to_remove_set.contains(view_count.docid);
+    });
+    for (const feedstore::DocView& view : all_views) {
+      if (docids_to_remove_set.contains(view.docid())) {
+        digest.old_doc_views.push_back(view);
+      }
+    }
+    DCHECK_EQ(digest.doc_view_counts.size(),
+              GetFeedConfig().max_docviews_to_send);
+  }
+  return digest;
+}
+}  // namespace internal
+
+bool DocViewCount::operator==(const DocViewCount& rhs) const {
+  return std::tie(docid, view_count) == std::tie(rhs.docid, rhs.view_count);
+}
+
+void ReadDocViewDigestIfEnabled(
+    FeedStream& feed_stream,
+    base::OnceCallback<void(DocViewDigest)> callback) {
+  if (!IsEnabled(feed_stream)) {
+    std::move(callback).Run({});
+    return;
+  }
+  auto callback_wrapper = [](base::OnceCallback<void(DocViewDigest)> callback,
+                             std::vector<feedstore::DocView> all_views) {
+    std::move(callback).Run(internal::CreateDigest(std::move(all_views)));
+  };
+  feed_stream.GetStore().ReadDocViews(
+      base::BindOnce(callback_wrapper, std::move(callback)));
+}
+
+void RemoveOldDocViews(const DocViewDigest& doc_view_digest,
+                       FeedStore& feed_store) {
+  feed_store.RemoveDocViews(doc_view_digest.old_doc_views);
+}
+
+void WriteDocViewIfEnabled(FeedStream& feed_stream, uint64_t docid) {
+  if (!IsEnabled(feed_stream)) {
+    return;
+  }
+  feed_stream.GetStore().WriteDocView(feedstore::CreateDocView(docid));
+}
+
+DocViewDigest::DocViewDigest() = default;
+DocViewDigest::~DocViewDigest() = default;
+DocViewDigest::DocViewDigest(const DocViewDigest&) = default;
+DocViewDigest::DocViewDigest(DocViewDigest&&) = default;
+DocViewDigest& DocViewDigest::operator=(const DocViewDigest&) = default;
+DocViewDigest& DocViewDigest::operator=(DocViewDigest&&) = default;
+
+std::ostream& operator<<(std::ostream& os, const DocViewCount& doc_view_count) {
+  return os << "DocViewCount{docid: " << doc_view_count.docid
+            << " count: " << doc_view_count.view_count << " }";
+}
+
+}  // namespace feed
diff --git a/components/feed/core/v2/view_demotion.h b/components/feed/core/v2/view_demotion.h
new file mode 100644
index 0000000..f9b7248
--- /dev/null
+++ b/components/feed/core/v2/view_demotion.h
@@ -0,0 +1,60 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef COMPONENTS_FEED_CORE_V2_VIEW_DEMOTION_H_
+#define COMPONENTS_FEED_CORE_V2_VIEW_DEMOTION_H_
+
+#include <iosfwd>
+#include <vector>
+#include "base/functional/callback_forward.h"
+
+namespace feedstore {
+class DocView;
+}
+namespace feed {
+struct DocViewDigest;
+
+namespace internal {
+DocViewDigest CreateDigest(std::vector<feedstore::DocView> all_views);
+}
+
+class FeedStream;
+
+struct DocViewCount {
+  // Uniquely identifies content.
+  uint64_t docid = 0;
+  // Number of views.
+  uint64_t view_count = 0;
+
+  bool operator==(const DocViewCount& rhs) const;
+};
+
+std::ostream& operator<<(std::ostream& os, const DocViewCount& doc_view_count);
+
+// Summarizes stored document view data.
+struct DocViewDigest {
+  DocViewDigest();
+  ~DocViewDigest();
+  DocViewDigest(const DocViewDigest&);
+  DocViewDigest(DocViewDigest&&);
+  DocViewDigest& operator=(const DocViewDigest&);
+  DocViewDigest& operator=(DocViewDigest&&);
+
+  // Document views which are not expired.
+  std::vector<DocViewCount> doc_view_counts;
+  // Document views which are expired, and should be removed.
+  std::vector<feedstore::DocView> old_doc_views;
+};
+
+// Reads the DocViewDigest if view demotion is enabled. Otherwise, `callback` is
+// called immediately with an empty digest.
+void ReadDocViewDigestIfEnabled(
+    FeedStream& feed_stream,
+    base::OnceCallback<void(DocViewDigest)> callback);
+
+// Records a document view if view demotion is enabled.
+void WriteDocViewIfEnabled(FeedStream& feed_stream, uint64_t docid);
+
+}  // namespace feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_VIEW_DEMOTION_H_
diff --git a/components/feed/core/v2/view_demotion_unittest.cc b/components/feed/core/v2/view_demotion_unittest.cc
new file mode 100644
index 0000000..195dbdd
--- /dev/null
+++ b/components/feed/core/v2/view_demotion_unittest.cc
@@ -0,0 +1,106 @@
+// 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 "components/feed/core/v2/view_demotion.h"
+
+#include "base/test/task_environment.h"
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/v2/config.h"
+#include "components/feed/core/v2/feedstore_util.h"
+#include "components/feed/core/v2/test/proto_printer.h"
+#include "components/feed/core/v2/test/test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace feed {
+namespace {
+using feedstore::CreateDocView;
+using testing::UnorderedElementsAre;
+
+class ViewDemotionTest : public testing::Test {
+ public:
+  ViewDemotionTest() {
+    feed::Config config;
+    config.max_docviews_to_send = 5;
+    SetFeedConfigForTesting(config);
+  }
+  void SetUp() override {
+    // Make sure time isn't 0, so we can have events in the past.
+    task_environment_.AdvanceClock(base::Hours(10000));
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_F(ViewDemotionTest, OnlyNewAreCounted) {
+  base::Time now = base::Time::Now();
+
+  auto view1 = CreateDocView(10, now - base::Hours(71));
+  // Too old.
+  auto view2 = CreateDocView(11, now - base::Hours(73));
+  // From the future.
+  auto view3 = CreateDocView(12, now + base::Hours(1));
+  DocViewDigest digest = internal::CreateDigest({view1, view2, view3});
+  EXPECT_THAT(digest.doc_view_counts,
+              UnorderedElementsAre(DocViewCount{view1.docid(), 1}));
+  EXPECT_THAT(digest.old_doc_views,
+              UnorderedElementsAre(EqualsProto(view2), EqualsProto(view3)));
+}
+
+TEST_F(ViewDemotionTest, MultipleViewsOfOneDocument) {
+  base::Time now = base::Time::Now();
+  // 2/3 views are fresh.
+  auto view1 = CreateDocView(10, now - base::Hours(71));
+  auto view2 = CreateDocView(10, now - base::Hours(1));
+  auto view3 = CreateDocView(10, now - base::Hours(73));
+  DocViewDigest digest = internal::CreateDigest({view1, view2, view3});
+  EXPECT_THAT(digest.doc_view_counts,
+              UnorderedElementsAre(DocViewCount{view1.docid(), 2}));
+  EXPECT_THAT(digest.old_doc_views, UnorderedElementsAre(EqualsProto(view3)));
+}
+
+TEST_F(ViewDemotionTest, TotalViewsOfFewDocsCanExceedLimit) {
+  base::Time now = base::Time::Now();
+  DocViewDigest digest = internal::CreateDigest({
+      CreateDocView(10, now - base::Hours(1)),
+      CreateDocView(10, now - base::Hours(2)),
+      CreateDocView(10, now - base::Hours(3)),
+      CreateDocView(10, now - base::Hours(4)),
+      CreateDocView(10, now - base::Hours(5)),
+      CreateDocView(10, now - base::Hours(6)),
+      CreateDocView(10, now - base::Hours(7)),
+  });
+  EXPECT_THAT(digest.doc_view_counts,
+              UnorderedElementsAre(DocViewCount{10, 7}));
+  EXPECT_THAT(digest.old_doc_views, testing::IsEmpty());
+}
+
+TEST_F(ViewDemotionTest, TotalDocumentsExceedsLimit) {
+  base::Time now = base::Time::Now();
+  std::vector<feedstore::DocView> views = {
+      CreateDocView(9, now - base::Hours(8)),
+      CreateDocView(10, now - base::Hours(1)),
+      CreateDocView(11, now - base::Hours(2)),
+      CreateDocView(12, now - base::Hours(3)),
+      CreateDocView(13, now - base::Hours(4)),
+      CreateDocView(14, now - base::Hours(5)),
+      CreateDocView(15, now - base::Hours(6)),
+      CreateDocView(16, now - base::Hours(7)),
+  };
+  DocViewDigest digest = internal::CreateDigest(views);
+  // max_docviews_to_send = 5
+  EXPECT_THAT(digest.doc_view_counts,
+              UnorderedElementsAre(DocViewCount{10, 1}, DocViewCount{11, 1},
+                                   DocViewCount{12, 1}, DocViewCount{13, 1},
+                                   DocViewCount{14, 1}));
+
+  EXPECT_THAT(digest.old_doc_views,
+              UnorderedElementsAre(EqualsProto(views[0]), EqualsProto(views[6]),
+                                   EqualsProto(views[7])));
+}
+
+}  // namespace
+}  // namespace feed
diff --git a/components/feed/features.gni b/components/feed/features.gni
index 26fb5c7c55..008b80c 100644
--- a/components/feed/features.gni
+++ b/components/feed/features.gni
@@ -5,10 +5,4 @@
 declare_args() {
   # Whether Feed is enabled in the build.
   enable_feed_v2 = !is_ios
-
-  # Whether to include Feed in ChromeModern builds.
-  enable_feed_v2_modern = true
-
-  # Whether to include Feed as a DFM in ChromeModern builds.
-  dfmify_feed_v2_modern = false
 }
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index f049a40..94aab86 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -173,4 +173,9 @@
 BASE_FEATURE(kFeedUserInteractionReliabilityReport,
              "FeedUserInteractionReliabilityReport",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kFeedSignedOutViewDemotion,
+             "FeedSignedOutViewDemotion",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace feed
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index 1ecbf906..a1cb07c 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -148,6 +148,9 @@
 // Feature that enables reporting feed user interaction reliability.
 BASE_DECLARE_FEATURE(kFeedUserInteractionReliabilityReport);
 
+// Feature that enables signed-out view demotion.
+BASE_DECLARE_FEATURE(kFeedSignedOutViewDemotion);
+
 }  // namespace feed
 
 #endif  // COMPONENTS_FEED_FEED_FEATURE_LIST_H_
diff --git a/components/feedback/redaction_tool/redaction_tool.cc b/components/feedback/redaction_tool/redaction_tool.cc
index 5aeb4a6..022fc6d 100644
--- a/components/feedback/redaction_tool/redaction_tool.cc
+++ b/components/feedback/redaction_tool/redaction_tool.cc
@@ -508,7 +508,9 @@
 };
 constexpr size_t kNumUnredactedMacs = std::size(kUnredactedMacAddresses);
 
-constexpr char kFeedbackRedactionToolHistogramName[] = "Feedback.RedactionTool";
+void RecordPIIRedactedHistogram(const PIIType pii_type) {
+  UMA_HISTOGRAM_ENUMERATION("Feedback.RedactionTool", pii_type);
+}
 
 }  // namespace
 
@@ -639,13 +641,11 @@
     }
     skipped.AppendToString(&result);
     result += replacement_mac;
+    RecordPIIRedactedHistogram(PIIType::kMACAddress);
   }
 
   text.AppendToString(&result);
 
-  UMA_HISTOGRAM_ENUMERATION(kFeedbackRedactionToolHistogramName,
-                            PIIType::kMACAddress);
-
   return result;
 }
 
@@ -700,13 +700,12 @@
     }
 
     result += replacement_hash;
+
+    RecordPIIRedactedHistogram(PIIType::kStableIdentifier);
   }
 
   text.AppendToString(&result);
 
-  UMA_HISTOGRAM_ENUMERATION(kFeedbackRedactionToolHistogramName,
-                            PIIType::kStableIdentifier);
-
   return result;
 }
 
@@ -770,13 +769,11 @@
       (*detected)[PIIType::kAndroidAppStoragePath].insert(
           app_specific.as_string());
     }
+    RecordPIIRedactedHistogram(PIIType::kAndroidAppStoragePath);
   }
 
   text.AppendToString(&result);
 
-  UMA_HISTOGRAM_ENUMERATION(kFeedbackRedactionToolHistogramName,
-                            PIIType::kAndroidAppStoragePath);
-
   return result;
 #else
   return input;
@@ -847,12 +844,10 @@
     pre_matched_id.AppendToString(&result);
     result += replacement_id;
     post_matched_id.AppendToString(&result);
+    RecordPIIRedactedHistogram(pattern.pii_type);
   }
   text.AppendToString(&result);
 
-  UMA_HISTOGRAM_ENUMERATION(kFeedbackRedactionToolHistogramName,
-                            pattern.pii_type);
-
   return result;
 }
 
@@ -969,12 +964,11 @@
 
     skipped.AppendToString(&result);
     result += replacement_id;
+
+    RecordPIIRedactedHistogram(pattern.pii_type);
   }
   text.AppendToString(&result);
 
-  UMA_HISTOGRAM_ENUMERATION(kFeedbackRedactionToolHistogramName,
-                            pattern.pii_type);
-
   return result;
 }
 
diff --git a/components/global_media_controls/public/media_session_notification_item.h b/components/global_media_controls/public/media_session_notification_item.h
index 5f7cdbe..645b6e8 100644
--- a/components/global_media_controls/public/media_session_notification_item.h
+++ b/components/global_media_controls/public/media_session_notification_item.h
@@ -10,6 +10,7 @@
 #include "base/component_export.h"
 #include "base/containers/flat_set.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index eb282ad..90dbf6c1 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -13,10 +13,7 @@
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
 #include "components/history_clusters/core/features.h"
-#include "components/history_clusters/core/history_clusters_prefs.h"
-#include "components/history_clusters/core/history_clusters_service.h"
 #include "components/history_clusters/core/on_device_clustering_features.h"
-#include "components/prefs/pref_service.h"
 #include "components/search/ntp_features.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -517,20 +514,6 @@
          base::Contains(allowlist, l10n_util::GetLanguage(application_locale));
 }
 
-bool IsJourneysEnabledInOmnibox(HistoryClustersService* service,
-                                PrefService* prefs) {
-  if (!service)
-    return false;
-
-  if (!service->IsJourneysEnabled())
-    return false;
-
-  if (!prefs->GetBoolean(history_clusters::prefs::kVisible))
-    return false;
-
-  return true;
-}
-
 const Config& GetConfig() {
   return GetConfigInternal();
 }
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 63e11b8f..0e834f9 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -10,8 +10,6 @@
 #include "base/containers/flat_set.h"
 #include "base/time/time.h"
 
-class PrefService;
-
 namespace history_clusters {
 
 namespace switches {
@@ -20,8 +18,6 @@
 
 }  // namespace switches
 
-class HistoryClustersService;
-
 // The default configuration. Always use |GetConfig()| to get the current
 // configuration.
 //
@@ -450,11 +446,6 @@
 bool IsApplicationLocaleSupportedByJourneys(
     const std::string& application_locale);
 
-// Checks some prerequisites for history cluster omnibox suggestions and
-// actions.
-bool IsJourneysEnabledInOmnibox(HistoryClustersService* service,
-                                PrefService* prefs);
-
 // Gets the current configuration.
 const Config& GetConfig();
 
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index 95e784f..2302fe1 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -167,7 +167,7 @@
 void HistoryClustersService::Shutdown() {}
 
 bool HistoryClustersService::IsJourneysEnabled() const {
-  return is_journeys_enabled_;
+  return is_journeys_enabled_ && pref_service_->GetBoolean(prefs::kVisible);
 }
 
 // static
@@ -245,6 +245,7 @@
     bool recluster,
     QueryClustersCallback callback) {
   if (!IsJourneysEnabled()) {
+    // TODO(crbug/1441974): Make this into a CHECK after verifying all callers.
     std::move(callback).Run({}, QueryClustersContinuationParams::DoneParams());
     return nullptr;
   }
diff --git a/components/history_clusters/core/history_clusters_service.h b/components/history_clusters/core/history_clusters_service.h
index 3c70e37..7bc876ad 100644
--- a/components/history_clusters/core/history_clusters_service.h
+++ b/components/history_clusters/core/history_clusters_service.h
@@ -86,7 +86,9 @@
   // Returns true if the Journeys feature is enabled for the current application
   // locale. This is a cached wrapper of `IsJourneysEnabled()` within features.h
   // that's already evaluated against the g_browser_process application locale.
-  bool IsJourneysEnabled() const;
+  // This now also includes checking the pref for whether Journeys is visible to
+  // the user. Virtual for testing.
+  virtual bool IsJourneysEnabled() const;
 
   // Returns true if the Journeys use of Images is enabled.
   static bool IsJourneysImagesEnabled();
@@ -133,7 +135,8 @@
   //   if the caller wants the newest visits.
   // - `recluster`, if true, forces reclustering as if
   //   `persist_clusters_in_history_db` were false.
-  // Virtual for testing.
+  // The caller is responsible for checking `IsJourneysEnabled()` before calling
+  // this method. Virtual for testing.
   virtual std::unique_ptr<HistoryClustersServiceTask> QueryClusters(
       ClusteringRequestSource clustering_request_source,
       QueryClustersFilterParams filter_params,
diff --git a/components/history_clusters/core/test_history_clusters_service.cc b/components/history_clusters/core/test_history_clusters_service.cc
index a4af770..f95934bd1 100644
--- a/components/history_clusters/core/test_history_clusters_service.cc
+++ b/components/history_clusters/core/test_history_clusters_service.cc
@@ -19,6 +19,10 @@
                              /*pref_service=*/nullptr) {}
 TestHistoryClustersService::~TestHistoryClustersService() = default;
 
+bool TestHistoryClustersService::IsJourneysEnabled() const {
+  return is_journeys_enabled_;
+}
+
 std::unique_ptr<HistoryClustersServiceTask>
 TestHistoryClustersService::QueryClusters(
     ClusteringRequestSource clustering_request_source,
@@ -38,6 +42,11 @@
   return nullptr;
 }
 
+void TestHistoryClustersService::SetIsJourneysEnabled(
+    bool is_journeys_enabled) {
+  is_journeys_enabled_ = is_journeys_enabled;
+}
+
 void TestHistoryClustersService::SetClustersToReturn(
     const std::vector<history::Cluster>& clusters,
     bool exhausted_all_visits) {
diff --git a/components/history_clusters/core/test_history_clusters_service.h b/components/history_clusters/core/test_history_clusters_service.h
index 607eb29..17cd2e4a 100644
--- a/components/history_clusters/core/test_history_clusters_service.h
+++ b/components/history_clusters/core/test_history_clusters_service.h
@@ -20,6 +20,7 @@
   ~TestHistoryClustersService() override;
 
   // HistoryClustersService:
+  bool IsJourneysEnabled() const override;
   std::unique_ptr<HistoryClustersServiceTask> QueryClusters(
       ClusteringRequestSource clustering_request_source,
       QueryClustersFilterParams filter_params,
@@ -28,6 +29,9 @@
       bool recluster,
       QueryClustersCallback callback) override;
 
+  // Sets whether Journeys is enabled.
+  void SetIsJourneysEnabled(bool is_journeys_enabled);
+
   // Sets `clusters` to be the clusters that always get returned when
   // `QueryClusters()` is called. If `exhausted_all_visits` is true, the next
   // query will invoke its callback using
@@ -36,6 +40,7 @@
                            bool exhausted_all_visits = true);
 
  private:
+  bool is_journeys_enabled_ = true;
   std::vector<history::Cluster> clusters_;
   bool next_query_is_done_ = false;
 };
diff --git a/components/keep_alive_registry/OWNERS b/components/keep_alive_registry/OWNERS
index c9c85d9..033a14f 100644
--- a/components/keep_alive_registry/OWNERS
+++ b/components/keep_alive_registry/OWNERS
@@ -1,2 +1 @@
-michaelpg@chromium.org
 nicolaso@chromium.org
diff --git a/components/language/ios/browser/ios_language_detection_tab_helper.h b/components/language/ios/browser/ios_language_detection_tab_helper.h
index c730cac..4d91c25 100644
--- a/components/language/ios/browser/ios_language_detection_tab_helper.h
+++ b/components/language/ios/browser/ios_language_detection_tab_helper.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_LANGUAGE_IOS_BROWSER_IOS_LANGUAGE_DETECTION_TAB_HELPER_H_
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "components/prefs/pref_member.h"
diff --git a/components/lens/lens_url_utils.cc b/components/lens/lens_url_utils.cc
index afea2e8..f106aa8 100644
--- a/components/lens/lens_url_utils.cc
+++ b/components/lens/lens_url_utils.cc
@@ -39,6 +39,8 @@
 constexpr char kOneLensDesktopWebFullscreen[] = "df";
 constexpr char kOneLensAmbientVisualSearchWebFullscreen[] = "avsf";
 constexpr char kChromeSearchCompanion[] = "csc";
+constexpr char kViewportWidthQueryParameter[] = "vpw";
+constexpr char kViewportHeightQueryParameter[] = "vph";
 
 void AppendQueryParam(std::string* query_string,
                       const char name[],
@@ -52,7 +54,8 @@
 std::map<std::string, std::string> GetLensQueryParametersMap(
     lens::EntryPoint ep,
     lens::RenderingEnvironment re,
-    bool is_side_panel_request) {
+    bool is_side_panel_request,
+    gfx::Size side_panel_initial_size_upper_bound) {
   std::map<std::string, std::string> query_parameters;
   switch (ep) {
     case lens::CHROME_OPEN_NEW_TAB_SIDE_PANEL:
@@ -101,6 +104,21 @@
       // Empty strings are ignored when query parameters are built.
       break;
   }
+  if (is_side_panel_request) {
+    const int side_panel_initial_width =
+        side_panel_initial_size_upper_bound.width();
+    const int side_panel_initial_height =
+        side_panel_initial_size_upper_bound.height();
+    if (side_panel_initial_width != 0) {
+      query_parameters.insert({kViewportWidthQueryParameter,
+                               base::NumberToString(side_panel_initial_width)});
+    }
+    if (side_panel_initial_height != 0) {
+      query_parameters.insert(
+          {kViewportHeightQueryParameter,
+           base::NumberToString(side_panel_initial_height)});
+    }
+  }
 
   query_parameters.insert({kSurfaceQueryParameter, kChromiumSurfaceProtoValue});
   int64_t current_time_ms = base::Time::Now().ToJavaTime();
@@ -139,21 +157,39 @@
   }
 }
 
-GURL AppendOrReplaceQueryParametersForLensRequest(const GURL& url,
-                                                  lens::EntryPoint ep,
-                                                  lens::RenderingEnvironment re,
-                                                  bool is_side_panel_request) {
+GURL AppendOrReplaceQueryParametersForLensRequest(
+    const GURL& url,
+    lens::EntryPoint ep,
+    lens::RenderingEnvironment re,
+    bool is_side_panel_request,
+    const gfx::Size& side_panel_initial_size_upper_bound) {
   GURL modified_url(url);
   for (auto const& param :
-       GetLensQueryParametersMap(ep, re, is_side_panel_request))
+       GetLensQueryParametersMap(ep, re, is_side_panel_request,
+                                 side_panel_initial_size_upper_bound)) {
     modified_url = net::AppendOrReplaceQueryParameter(modified_url, param.first,
                                                       param.second);
+  }
+
+  // Remove the viewport width and height params if the given size is zero or
+  // the request is not a side panel request.
+  if (!is_side_panel_request ||
+      side_panel_initial_size_upper_bound.width() == 0) {
+    modified_url = net::AppendOrReplaceQueryParameter(
+        modified_url, kViewportWidthQueryParameter, absl::nullopt);
+  }
+  if (!is_side_panel_request ||
+      side_panel_initial_size_upper_bound.height() == 0) {
+    modified_url = net::AppendOrReplaceQueryParameter(
+        modified_url, kViewportHeightQueryParameter, absl::nullopt);
+  }
   return modified_url;
 }
 
 std::string GetQueryParametersForLensRequest(
     lens::EntryPoint ep,
     bool is_side_panel_request,
+    const gfx::Size& side_panel_initial_size_upper_bound,
     bool is_full_screen_region_search_request,
     bool is_companion_request) {
   auto re = GetRenderingEnvironment(is_side_panel_request,
@@ -161,8 +197,10 @@
                                     is_companion_request);
   std::string query_string;
   for (auto const& param :
-       GetLensQueryParametersMap(ep, re, is_side_panel_request))
+       GetLensQueryParametersMap(ep, re, is_side_panel_request,
+                                 side_panel_initial_size_upper_bound)) {
     AppendQueryParam(&query_string, param.first.c_str(), param.second.c_str());
+  }
   return query_string;
 }
 
diff --git a/components/lens/lens_url_utils.h b/components/lens/lens_url_utils.h
index 5713e1d..3b22ffd 100644
--- a/components/lens/lens_url_utils.h
+++ b/components/lens/lens_url_utils.h
@@ -10,6 +10,7 @@
 #include "components/lens/lens_entrypoints.h"
 #include "components/lens/lens_metadata.mojom.h"
 #include "components/lens/lens_rendering_environment.h"
+#include "ui/gfx/geometry/size_f.h"
 
 class GURL;
 
@@ -31,18 +32,24 @@
     const std::vector<lens::mojom::LatencyLogPtr>& log_data);
 
 // Returns a modified GURL with appended or replaced parameters depending on the
-// entrypoint and other parameters.
+// entrypoint and other parameters. The width and height of the side panel
+// initial size are ignored if they are 0 or if the request is not a side panel
+// request.
 extern GURL AppendOrReplaceQueryParametersForLensRequest(
     const GURL& url,
     lens::EntryPoint ep,
     lens::RenderingEnvironment re,
-    bool is_side_panel_request);
+    bool is_side_panel_request,
+    const gfx::Size& side_panel_initial_size_upper_bound);
 
 // Returns a query string with all relevant query parameters. Needed for when a
-// GURL is unavailable to append to.
+// GURL is unavailable to append to. The width and height of the side panel
+// initial size are ignored if they are 0 or if the request is not a side panel
+// request.
 extern std::string GetQueryParametersForLensRequest(
     lens::EntryPoint ep,
     bool is_side_panel_request,
+    const gfx::Size& side_panel_initial_size_upper_bound,
     bool is_full_screen_region_search_request,
     bool is_companion_request = false);
 
diff --git a/components/lens/lens_url_utils_unittest.cc b/components/lens/lens_url_utils_unittest.cc
index b78d7ef..6f702c4 100644
--- a/components/lens/lens_url_utils_unittest.cc
+++ b/components/lens/lens_url_utils_unittest.cc
@@ -16,12 +16,38 @@
 
 namespace lens {
 
+TEST(LensUrlUtilsTest, NonSidePanelRequestHasNoSidePanelSizeParams) {
+  lens::EntryPoint lens_region_search_ep =
+      lens::EntryPoint::CHROME_REGION_SEARCH_MENU_ITEM;
+  std::string query_param = lens::GetQueryParametersForLensRequest(
+      lens_region_search_ep, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(10, 10),
+      /*is_full_screen_region_search_request=*/false);
+
+  // Despite passing in a nonzero size, there should not be any side panel
+  // viewport size params.
+  EXPECT_THAT(query_param, MatchesRegex("ep=crs&re=df&s=4&st=\\d+"));
+}
+
+TEST(LensUrlUtilsTest, SidePanelRequesetHasSidePanelSizeParams) {
+  lens::EntryPoint lens_region_search_ep =
+      lens::EntryPoint::CHROME_REGION_SEARCH_MENU_ITEM;
+  std::string query_param = lens::GetQueryParametersForLensRequest(
+      lens_region_search_ep, /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(10, 10),
+      /*is_full_screen_region_search_request=*/false);
+
+  EXPECT_THAT(query_param,
+              MatchesRegex("ep=crs&re=dcsp&s=4&st=\\d+&vph=10&vpw=10"));
+}
+
 TEST(LensUrlUtilsTest, GetRegionSearchNewTabQueryParameterTest) {
   lens::EntryPoint lens_region_search_ep =
       lens::EntryPoint::CHROME_REGION_SEARCH_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_region_search_ep, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ false);
+      lens_region_search_ep, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=crs&re=df&s=4&st=\\d+"));
 }
 
@@ -29,8 +55,9 @@
   lens::EntryPoint lens_image_search_ep =
       lens::EntryPoint::CHROME_SEARCH_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_image_search_ep, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ false);
+      lens_image_search_ep, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=ccm&re=df&s=4&st=\\d+"));
 }
 
@@ -38,8 +65,9 @@
   lens::EntryPoint lens_image_translate_ep = lens::EntryPoint::
       CHROME_TRANSLATE_IMAGE_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_image_translate_ep, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ false);
+      lens_image_translate_ep, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=ctrcm&re=df&s=4&st=\\d+"));
 }
 
@@ -47,8 +75,9 @@
   lens::EntryPoint lens_region_search_ep =
       lens::EntryPoint::CHROME_REGION_SEARCH_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_region_search_ep, /* is_side_panel_request= */ true,
-      /* is_full_screen_region_search_request= */ false);
+      lens_region_search_ep, /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=crs&re=dcsp&s=4&st=\\d+"));
 }
 
@@ -56,8 +85,9 @@
   lens::EntryPoint lens_image_search_ep =
       lens::EntryPoint::CHROME_SEARCH_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_image_search_ep, /* is_side_panel_request= */ true,
-      /* is_full_screen_region_search_request= */ false);
+      lens_image_search_ep, /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=ccm&re=dcsp&s=4&st=\\d+"));
 }
 
@@ -65,8 +95,9 @@
   lens::EntryPoint lens_image_translate_ep = lens::EntryPoint::
       CHROME_TRANSLATE_IMAGE_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_image_translate_ep, /* is_side_panel_request= */ true,
-      /* is_full_screen_region_search_request= */ false);
+      lens_image_translate_ep, /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=ctrcm&re=dcsp&s=4&st=\\d+"));
 }
 
@@ -74,8 +105,9 @@
   lens::EntryPoint lens_open_new_tab_side_panel_ep =
       lens::EntryPoint::CHROME_OPEN_NEW_TAB_SIDE_PANEL;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_open_new_tab_side_panel_ep, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ false);
+      lens_open_new_tab_side_panel_ep, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("ep=cnts&re=df&s=4&st=\\d+"));
 }
 
@@ -83,22 +115,25 @@
   lens::EntryPoint lens_ep =
       lens::EntryPoint::CHROME_FULLSCREEN_SEARCH_MENU_ITEM;
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_ep, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ true);
+      lens_ep, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/true);
   EXPECT_THAT(query_param, MatchesRegex("ep=cfs&re=avsf&s=4&st=\\d+"));
 }
 
 TEST(LensUrlUtilsTest, GetUnknownEntryPointTest) {
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens::EntryPoint::UNKNOWN, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ false);
+      lens::EntryPoint::UNKNOWN, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("re=df&s=4&st=\\d+"));
 }
 
 TEST(LensUrlUtilsTest, GetUnknownEntryPointSidePanelTest) {
   std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens::EntryPoint::UNKNOWN, /* is_side_panel_request= */ true,
-      /* is_full_screen_region_search_request= */ false);
+      lens::EntryPoint::UNKNOWN, /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("re=dcsp&s=4&st=\\d+"));
 }
 
@@ -110,7 +145,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_region_search_ep, re,
-      /* is_side_panel_request= */ false);
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=crs&re=df&s=4&st=\\d+"));
 }
 
@@ -122,7 +158,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_image_search_ep, re,
-      /* is_side_panel_request= */ false);
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=ccm&re=df&s=4&st=\\d+"));
 }
 
@@ -134,7 +171,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_image_translate_ep, re,
-      /* is_side_panel_request= */ false);
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=ctrcm&re=df&s=4&st=\\d+"));
 }
 
@@ -146,7 +184,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_region_search_ep, re,
-      /* is_side_panel_request= */ true);
+      /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=crs&re=dcsp&s=4&st=\\d+"));
 }
 
@@ -158,7 +197,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_image_search_ep, re,
-      /* is_side_panel_request= */ true);
+      /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=ccm&re=dcsp&s=4&st=\\d+"));
 }
 
@@ -170,7 +210,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_image_translate_ep, re,
-      /* is_side_panel_request= */ true);
+      /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=ctrcm&re=dcsp&s=4&st=\\d+"));
 }
 
@@ -182,7 +223,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens_open_new_tab_side_panel_ep, re,
-      /* is_side_panel_request= */ false);
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=cnts&re=df&s=4&st=\\d+"));
 }
 
@@ -193,7 +235,8 @@
       lens::RenderingEnvironment::ONELENS_AMBIENT_VISUAL_SEARCH_WEB_FULLSCREEN;
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
-      original_url, lens_ep, re, /* is_side_panel_request= */ false);
+      original_url, lens_ep, re, /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=cfs&re=avsf&s=4&st=\\d+"));
 }
 
@@ -203,7 +246,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, lens::EntryPoint::UNKNOWN, re,
-      /* is_side_panel_request= */ false);
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("re=df&s=4&st=\\d+"));
 }
 
@@ -212,7 +256,8 @@
   GURL original_url = GURL("https://lens.google.com/");
   GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
       original_url, ep, lens::RenderingEnvironment::RENDERING_ENV_UNKNOWN,
-      /* is_side_panel_request= */ false);
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size());
   EXPECT_THAT(url.query(), MatchesRegex("ep=crs&s=4&st=\\d+"));
 }
 
@@ -220,8 +265,9 @@
   std::vector<lens::mojom::LatencyLogPtr> log_data;
   std::string query_param = lens::GetQueryParametersForLensRequest(
       lens::EntryPoint::UNKNOWN,
-      /* is_side_panel_request= */ true,
-      /* is_full_screen_region_search_request= */ false);
+      /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("re=dcsp&s=4&st=\\d+"));
   lens::AppendLogsQueryParam(&query_param, std::move(log_data));
   EXPECT_THAT(query_param, MatchesRegex("re=dcsp&s=4&st=\\d+"));
@@ -234,11 +280,40 @@
       lens::mojom::ImageFormat::ORIGINAL, base::Time::Now()));
   std::string query_param = lens::GetQueryParametersForLensRequest(
       lens::EntryPoint::UNKNOWN,
-      /* is_side_panel_request= */ true,
-      /* is_full_screen_region_search_request= */ false);
+      /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(),
+      /*is_full_screen_region_search_request=*/false);
   EXPECT_THAT(query_param, MatchesRegex("re=dcsp&s=4&st=\\d+"));
   lens::AppendLogsQueryParam(&query_param, std::move(log_data));
   EXPECT_THAT(query_param, MatchesRegex("re=dcsp&s=4&st=\\d+&lm.+"));
 }
 
+TEST(LensUrlUtilsTest, AppendSidePanelViewportSizeTest) {
+  lens::EntryPoint lens_image_search_ep =
+      lens::EntryPoint::CHROME_SEARCH_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM;
+  lens::RenderingEnvironment re =
+      lens::RenderingEnvironment::ONELENS_DESKTOP_WEB_CHROME_SIDE_PANEL;
+  GURL original_url = GURL("https://lens.google.com/");
+  GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
+      original_url, lens_image_search_ep, re,
+      /*is_side_panel_request=*/true,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(10, 10));
+  EXPECT_THAT(url.query(),
+              MatchesRegex("ep=ccm&re=dcsp&s=4&st=\\d+&vph=10&vpw=10"));
+}
+
+TEST(LensUrlUtilsTest, AppendNonSidePanelSettingsRemovesViewportSizeTest) {
+  lens::EntryPoint lens_image_search_ep =
+      lens::EntryPoint::CHROME_SEARCH_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM;
+  lens::RenderingEnvironment re =
+      lens::RenderingEnvironment::ONELENS_DESKTOP_WEB_FULLSCREEN;
+  GURL original_url =
+      GURL("https://lens.google.com/search?p=123&vph=10&vpw=10");
+  GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
+      original_url, lens_image_search_ep, re,
+      /*is_side_panel_request=*/false,
+      /*side_panel_initial_size_upper_bound=*/gfx::Size(10, 10));
+  EXPECT_THAT(url.query(), MatchesRegex("p=123&ep=ccm&re=df&s=4&st=\\d+"));
+}
+
 }  // namespace lens
diff --git a/components/metrics/call_stack_profile_builder.cc b/components/metrics/call_stack_profile_builder.cc
index 9c204b7..1d43ef1 100644
--- a/components/metrics/call_stack_profile_builder.cc
+++ b/components/metrics/call_stack_profile_builder.cc
@@ -259,9 +259,17 @@
     module_id->set_name_md5_prefix(
         HashModuleFilename(module->GetDebugBasename()));
   }
+  // sampled_profile_ cannot be reused after it is cleared by this function.
+  // Check we still have the information from the constructor.
+  CHECK(sampled_profile_.has_process());
+  CHECK(sampled_profile_.has_thread());
+  CHECK(sampled_profile_.has_trigger_event());
 
   PassProfilesToMetricsProvider(profile_start_time_,
                                 std::move(sampled_profile_));
+  // Protobuffers are in an uncertain state after moving from; clear to get
+  // back to known state.
+  sampled_profile_.Clear();
 
   // Run the completed callback if there is one.
   if (!completed_callback_.is_null())
@@ -271,6 +279,7 @@
   stack_index_.clear();
   module_index_.clear();
   modules_.clear();
+  sample_timestamps_.clear();
 }
 
 // static
diff --git a/components/metrics/demographics/demographic_metrics_provider_unittest.cc b/components/metrics/demographics/demographic_metrics_provider_unittest.cc
index ab0ce6f..00d2d75da 100644
--- a/components/metrics/demographics/demographic_metrics_provider_unittest.cc
+++ b/components/metrics/demographics/demographic_metrics_provider_unittest.cc
@@ -64,7 +64,7 @@
         // Set an arbitrary disable reason to mimic sync feature being unable to
         // start.
         sync_service_->SetDisableReasons(
-            syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR);
+            {syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR});
         break;
 
       case SYNC_FEATURE_ENABLED:
@@ -88,8 +88,8 @@
 
       case SYNC_FEATURE_DISABLED_ON_CHROMEOS_ASH_VIA_DASHBOARD:
         sync_service_ = std::make_unique<syncer::TestSyncService>();
-        sync_service_->SetDisableReasons(syncer::SyncService::DisableReasonSet(
-            syncer::SyncService::DISABLE_REASON_USER_CHOICE));
+        sync_service_->SetDisableReasons(
+            {syncer::SyncService::DISABLE_REASON_USER_CHOICE});
 
         // On ChromeOS Ash, IsFirstSetupComplete gets cleared temporarily but
         // immediately afterwards, it gets set again with
diff --git a/components/metrics/structured/delegating_events_processor.cc b/components/metrics/structured/delegating_events_processor.cc
index 16211e3..a73156c 100644
--- a/components/metrics/structured/delegating_events_processor.cc
+++ b/components/metrics/structured/delegating_events_processor.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "components/metrics/structured/delegating_events_processor.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
 
 namespace metrics::structured {
 
@@ -34,4 +35,11 @@
   events_processors_.push_back(std::move(events_processor));
 }
 
+void DelegatingEventsProcessor::OnProvideIndependentMetrics(
+    ChromeUserMetricsExtension* uma_proto) {
+  for (auto& events_processor : events_processors_) {
+    events_processor->OnProvideIndependentMetrics(uma_proto);
+  }
+}
+
 }  // namespace metrics::structured
diff --git a/components/metrics/structured/delegating_events_processor.h b/components/metrics/structured/delegating_events_processor.h
index 6ac66b46..6a8a1f87 100644
--- a/components/metrics/structured/delegating_events_processor.h
+++ b/components/metrics/structured/delegating_events_processor.h
@@ -11,6 +11,11 @@
 #include "components/metrics/structured/events_processor_interface.h"
 
 namespace metrics::structured {
+namespace {
+
+using ::metrics::ChromeUserMetricsExtension;
+
+}
 
 // DelegatingEventsProcessor manages a set of other EventsProcessorInterfaces.
 // Calls to this events processor are forwarded to all of the registered events
@@ -27,6 +32,8 @@
   // EventsProcessor:
   bool ShouldProcessOnEventRecord(const Event& event) override;
   void OnEventsRecord(Event* event) override;
+  void OnProvideIndependentMetrics(
+      ChromeUserMetricsExtension* uma_proto) override;
 
  private:
   std::vector<std::unique_ptr<EventsProcessorInterface>> events_processors_;
diff --git a/components/metrics/structured/events_processor_interface.h b/components/metrics/structured/events_processor_interface.h
index 955cf875..29778ee 100644
--- a/components/metrics/structured/events_processor_interface.h
+++ b/components/metrics/structured/events_processor_interface.h
@@ -6,11 +6,18 @@
 #define COMPONENTS_METRICS_STRUCTURED_EVENTS_PROCESSOR_INTERFACE_H_
 
 #include "components/metrics/structured/event.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
 
 namespace metrics::structured {
 
-// An interface allowing different classes to add fields to events after the
-// events are recorded by a client.
+namespace {
+
+using ::metrics::ChromeUserMetricsExtension;
+
+}
+
+// An interface allowing different classes to add fields and metadata to events
+// after the events are recorded by a client.
 class EventsProcessorInterface {
  public:
   EventsProcessorInterface() = default;
@@ -27,6 +34,11 @@
 
   // Processes |event|. Note that this function may mutate |event|.
   virtual void OnEventsRecord(Event* event) = 0;
+
+  // Attach metadata when |ProvideIndependentMetrics| is called from the
+  // MetricsService. This will be called before events are attached.
+  virtual void OnProvideIndependentMetrics(
+      ChromeUserMetricsExtension* uma_proto) = 0;
 };
 
 }  // namespace metrics::structured
diff --git a/components/metrics/structured/recorder.cc b/components/metrics/structured/recorder.cc
index 03a301a..28fb30d9 100644
--- a/components/metrics/structured/recorder.cc
+++ b/components/metrics/structured/recorder.cc
@@ -114,4 +114,9 @@
   delegating_events_processor_.AddEventsProcessor(std::move(events_processor));
 }
 
+void Recorder::OnProvideIndependentMetrics(
+    ChromeUserMetricsExtension* uma_proto) {
+  delegating_events_processor_.OnProvideIndependentMetrics(uma_proto);
+}
+
 }  // namespace metrics::structured
diff --git a/components/metrics/structured/recorder.h b/components/metrics/structured/recorder.h
index 703e08bf..18b6a95 100644
--- a/components/metrics/structured/recorder.h
+++ b/components/metrics/structured/recorder.h
@@ -20,6 +20,11 @@
 }
 
 namespace metrics::structured {
+namespace {
+
+using ::metrics::ChromeUserMetricsExtension;
+
+}
 
 // Recorder is a singleton to help communicate with the
 // StructuredMetricsProvider. It serves three purposes:
@@ -37,6 +42,10 @@
 //
 // Recorder is embedded within StructuredMetricsClient for Ash Chrome and should
 // only be used in Ash Chrome.
+//
+// TODO(b/282031543): Remove this class and merge remaining logic into
+// structured_metrics_recorder.h since the Record() is exposed via
+// StructuredMetricsClient interface now.
 class Recorder {
  public:
   class RecorderImpl : public base::CheckedObserver {
@@ -92,6 +101,9 @@
   void AddEventsProcessor(
       std::unique_ptr<EventsProcessorInterface> events_processor);
 
+  // Modifies |uma_proto| before the log is sent.
+  void OnProvideIndependentMetrics(ChromeUserMetricsExtension* uma_proto);
+
  private:
   friend class base::NoDestructor<Recorder>;
 
diff --git a/components/metrics/structured/structured_metrics_provider.cc b/components/metrics/structured/structured_metrics_provider.cc
index ff49d00..0613e3a 100644
--- a/components/metrics/structured/structured_metrics_provider.cc
+++ b/components/metrics/structured/structured_metrics_provider.cc
@@ -91,6 +91,7 @@
 
   last_provided_independent_metrics_ = base::Time::Now();
 
+  Recorder::GetInstance()->OnProvideIndependentMetrics(uma_proto);
   recorder().ProvideEventMetrics(*uma_proto);
 
   // Independent events should not be associated with the client_id, so clear
diff --git a/components/mirroring/service/openscreen_session_host.h b/components/mirroring/service/openscreen_session_host.h
index bfb22ac..bed384e7 100644
--- a/components/mirroring/service/openscreen_session_host.h
+++ b/components/mirroring/service/openscreen_session_host.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_MIRRORING_SERVICE_OPENSCREEN_SESSION_HOST_H_
 
 #include "base/component_export.h"
+#include "base/gtest_prod_util.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
diff --git a/components/omnibox/browser/BUILD.gn b/components/omnibox/browser/BUILD.gn
index 3f89af6..0116332 100644
--- a/components/omnibox/browser/BUILD.gn
+++ b/components/omnibox/browser/BUILD.gn
@@ -511,21 +511,26 @@
       "android/java/src/org/chromium/components/omnibox/AutocompleteMatch.java",
       "android/java/src/org/chromium/components/omnibox/AutocompleteResult.java",
       "android/java/src/org/chromium/components/omnibox/AutocompleteSchemeClassifier.java",
+      "android/java/src/org/chromium/components/omnibox/OmniboxMetrics.java",
       "android/java/src/org/chromium/components/omnibox/OmniboxUrlEmphasizer.java",
       "android/java/src/org/chromium/components/omnibox/SecurityButtonAnimationDelegate.java",
       "android/java/src/org/chromium/components/omnibox/SecurityStatusIcon.java",
       "android/java/src/org/chromium/components/omnibox/SuggestionAnswer.java",
       "android/java/src/org/chromium/components/omnibox/action/HistoryClustersAction.java",
       "android/java/src/org/chromium/components/omnibox/action/OmniboxAction.java",
+      "android/java/src/org/chromium/components/omnibox/action/OmniboxActionDelegate.java",
       "android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggest.java",
       "android/java/src/org/chromium/components/omnibox/action/OmniboxPedal.java",
+      "android/java/src/org/chromium/components/omnibox/suggestions/OmniboxSuggestionUiType.java",
     ]
 
     resources_package = "org.chromium.components.omnibox"
     deps = [
       ":java_resources",
+      "//base:base_java",
       "//base:jni_java",
       "//build/android:build_java",
+      "//components/browser_ui/settings/android:java",
       "//components/browser_ui/styles/android:java_resources",
       "//components/browser_ui/widget/android:java",
       "//components/embedder_support/android:util_java",
@@ -536,6 +541,7 @@
       "//third_party/androidx:androidx_annotation_annotation_java",
       "//third_party/androidx:androidx_collection_collection_java",
       "//third_party/androidx:androidx_core_core_java",
+      "//third_party/metrics_proto:metrics_proto_java",
       "//ui/android:ui_full_java",
       "//url:gurl_java",
     ]
@@ -558,11 +564,15 @@
       ":browser_java",
       ":java_resources",
       ":junit_test_support",
+      "//base:base_java",
       "//base:base_java_test_support",
       "//base:base_junit_test_support",
+      "//components/browser_ui/settings/android:java",
       "//components/browser_ui/styles/android:java_resources",
+      "//components/embedder_support/android:util_java",
       "//third_party/android_deps:protobuf_lite_runtime_java",
       "//third_party/junit:junit",
+      "//third_party/mockito:mockito_java",
       "//url:gurl_junit_shadows",
     ]
   }
@@ -701,6 +711,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "actions/omnibox_action_in_suggest_unittest.cc",
     "actions/omnibox_pedal_provider_unittest.cc",
     "actions/omnibox_pedal_unittest.cc",
     "answers_cache_unittest.cc",
diff --git a/components/omnibox/browser/actions/history_clusters_action.cc b/components/omnibox/browser/actions/history_clusters_action.cc
index 40848d1..af33871 100644
--- a/components/omnibox/browser/actions/history_clusters_action.cc
+++ b/components/omnibox/browser/actions/history_clusters_action.cc
@@ -21,7 +21,6 @@
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_result.h"
 #include "components/optimization_guide/core/entity_metadata.h"
-#include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "net/base/url_util.h"
 
@@ -188,7 +187,6 @@
 // Should be invoked after `AutocompleteResult::AttachPedalsToMatches()`.
 void AttachHistoryClustersActions(
     history_clusters::HistoryClustersService* service,
-    PrefService* prefs,
     AutocompleteResult& result) {
 #if BUILDFLAG(IS_IOS)
   // Compile out this method for Mobile, which doesn't omnibox actions yet.
@@ -196,8 +194,9 @@
   return;
 #else
 
-  if (!IsJourneysEnabledInOmnibox(service, prefs))
+  if (!service || !service->IsJourneysEnabled()) {
     return;
+  }
 
   if (!GetConfig().omnibox_action)
     return;
diff --git a/components/omnibox/browser/actions/history_clusters_action.h b/components/omnibox/browser/actions/history_clusters_action.h
index bad2d45..2412166 100644
--- a/components/omnibox/browser/actions/history_clusters_action.h
+++ b/components/omnibox/browser/actions/history_clusters_action.h
@@ -11,7 +11,6 @@
 
 struct AutocompleteMatch;
 class AutocompleteResult;
-class PrefService;
 
 namespace gfx {
 struct VectorIcon;
@@ -80,7 +79,6 @@
 // onto any relevant matches in `result`.
 void AttachHistoryClustersActions(
     history_clusters::HistoryClustersService* service,
-    PrefService* prefs,
     AutocompleteResult& result);
 
 }  // namespace history_clusters
diff --git a/components/omnibox/browser/actions/history_clusters_action_unittest.cc b/components/omnibox/browser/actions/history_clusters_action_unittest.cc
index d9018288..77dbb68 100644
--- a/components/omnibox/browser/actions/history_clusters_action_unittest.cc
+++ b/components/omnibox/browser/actions/history_clusters_action_unittest.cc
@@ -84,7 +84,7 @@
         /*url_loader_factory=*/nullptr,
         /*engagement_score_provider=*/nullptr,
         /*template_url_service=*/nullptr,
-        /*optimization_guide_decider=*/nullptr, /*pref_service=*/nullptr);
+        /*optimization_guide_decider=*/nullptr, &prefs_enabled_);
 
     history_clusters_service_test_api_ =
         std::make_unique<HistoryClustersServiceTestApi>(
@@ -94,12 +94,11 @@
   }
 
   void TestAttachHistoryClustersActions(std::vector<MatchData> matches_data,
-                                        HistoryClustersService* service,
-                                        TestingPrefServiceSimple* prefs) {
+                                        HistoryClustersService* service) {
     AutocompleteResult result;
     result.AppendMatches(CreateACMatches(matches_data));
 
-    AttachHistoryClustersActions(service, prefs, result);
+    AttachHistoryClustersActions(service, result);
 
     for (size_t i = 0; i < matches_data.size(); ++i) {
       bool has_history_clusters_action =
@@ -117,8 +116,12 @@
   }
 
   void TestAttachHistoryClustersActions(std::vector<MatchData> matches_data) {
-    TestAttachHistoryClustersActions(
-        matches_data, history_clusters_service_.get(), &prefs_enabled_);
+    TestAttachHistoryClustersActions(matches_data,
+                                     history_clusters_service_.get());
+  }
+
+  void SetHistoryClustersVisiblePref(bool value) {
+    prefs_enabled_.SetBoolean(history_clusters::prefs::kVisible, value);
   }
 
   base::test::TaskEnvironment task_environment_;
@@ -138,7 +141,7 @@
 TEST_F(HistoryClustersActionTest, AttachHistoryClustersActions) {
   {
     SCOPED_TRACE("Shouldn't add action if history cluster service is nullptr.");
-    TestAttachHistoryClustersActions({{}}, nullptr, &prefs_enabled_);
+    TestAttachHistoryClustersActions({{}}, nullptr);
   }
 
   {
@@ -160,11 +163,10 @@
   {
     SCOPED_TRACE("Shouldn't add action if `kVisible` pref is false.");
     SetUpWithConfig(search_actions_config_);
-    TestingPrefServiceSimple prefs_disabled;
-    prefs_disabled.registry()->RegisterBooleanPref(
-        history_clusters::prefs::kVisible, false);
-    TestAttachHistoryClustersActions({{}}, history_clusters_service_.get(),
-                                     &prefs_disabled);
+    SetHistoryClustersVisiblePref(false);
+    TestAttachHistoryClustersActions({{}}, history_clusters_service_.get());
+    // Reset this back to true for future tests.
+    SetHistoryClustersVisiblePref(true);
   }
 
   {
diff --git a/components/omnibox/browser/actions/omnibox_action_in_suggest.cc b/components/omnibox/browser/actions/omnibox_action_in_suggest.cc
index 5e8834c..3f86551b 100644
--- a/components/omnibox/browser/actions/omnibox_action_in_suggest.cc
+++ b/components/omnibox/browser/actions/omnibox_action_in_suggest.cc
@@ -7,6 +7,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/omnibox/browser/actions/omnibox_action_concepts.h"
+#include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
@@ -32,40 +33,64 @@
   kCall,
   kDirections,
   kWebsite,
+  kReviews,
 
   // Sentinel value. Must be set to the last valid ActionInSuggestUmaType.
-  kMaxValue = kWebsite
+  kMaxValue = kReviews
 };
 
 // Get the UMA action type from ActionInfo::ActionType.
 constexpr ActionInSuggestUmaType ToUmaActionType(
-    omnibox::ActionInfo_ActionType action_type) {
+    omnibox::ActionInfo::ActionType action_type) {
   switch (action_type) {
     case omnibox::ActionInfo_ActionType_CALL:
       return ActionInSuggestUmaType::kCall;
-
     case omnibox::ActionInfo_ActionType_DIRECTIONS:
       return ActionInSuggestUmaType::kDirections;
-
     case omnibox::ActionInfo_ActionType_WEBSITE:
       return ActionInSuggestUmaType::kWebsite;
-
-    default:
-      return ActionInSuggestUmaType::kUnknown;
+    case omnibox::ActionInfo_ActionType_REVIEWS:
+      return ActionInSuggestUmaType::kReviews;
   }
+  NOTREACHED() << "Unrecognized action type: " << action_type;
 }
 
+constexpr int ToActionHint(omnibox::ActionInfo::ActionType action_type) {
+  switch (action_type) {
+    case omnibox::ActionInfo_ActionType_CALL:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_HINT;
+    case omnibox::ActionInfo_ActionType_DIRECTIONS:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_HINT;
+    case omnibox::ActionInfo_ActionType_REVIEWS:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_HINT;
+    case omnibox::ActionInfo_ActionType_WEBSITE:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_HINT;
+  }
+  NOTREACHED() << "Unrecognized action type: " << action_type;
+}
+
+constexpr int ToActionContents(omnibox::ActionInfo::ActionType action_type) {
+  switch (action_type) {
+    case omnibox::ActionInfo_ActionType_CALL:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_CONTENTS;
+    case omnibox::ActionInfo_ActionType_DIRECTIONS:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_CONTENTS;
+    case omnibox::ActionInfo_ActionType_REVIEWS:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_CONTENTS;
+    case omnibox::ActionInfo_ActionType_WEBSITE:
+      return IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_CONTENTS;
+  }
+  NOTREACHED() << "Unrecognized action type: " << action_type;
+}
 }  // namespace
 
 OmniboxActionInSuggest::OmniboxActionInSuggest(omnibox::ActionInfo action_info)
-    : OmniboxAction(
-          OmniboxAction::LabelStrings(
-              base::UTF8ToUTF16(action_info.displayed_text()),
-              base::UTF8ToUTF16(action_info.displayed_text()),
-              l10n_util::GetStringUTF16(
-                  IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST_SUFFIX),
-              l10n_util::GetStringUTF16(IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST)),
-          {}),
+    : OmniboxAction(OmniboxAction::LabelStrings(
+                        ToActionHint(action_info.action_type()),
+                        ToActionContents(action_info.action_type()),
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST_SUFFIX,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST),
+                    {}),
       action_info_{std::move(action_info)} {}
 
 OmniboxActionInSuggest::~OmniboxActionInSuggest() = default;
diff --git a/components/omnibox/browser/actions/omnibox_action_in_suggest.h b/components/omnibox/browser/actions/omnibox_action_in_suggest.h
index f9cc694..a50ce41 100644
--- a/components/omnibox/browser/actions/omnibox_action_in_suggest.h
+++ b/components/omnibox/browser/actions/omnibox_action_in_suggest.h
@@ -30,6 +30,8 @@
   static const OmniboxActionInSuggest* FromAction(const OmniboxAction* action);
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(OmniboxActionInSuggestTest,
+                           ReportMetricsForUnknownType);
   ~OmniboxActionInSuggest() override;
 
   omnibox::ActionInfo action_info_{};
diff --git a/components/omnibox/browser/actions/omnibox_action_in_suggest_unittest.cc b/components/omnibox/browser/actions/omnibox_action_in_suggest_unittest.cc
new file mode 100644
index 0000000..db5cdd9a
--- /dev/null
+++ b/components/omnibox/browser/actions/omnibox_action_in_suggest_unittest.cc
@@ -0,0 +1,248 @@
+// 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 "components/omnibox/browser/actions/omnibox_action_in_suggest.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "components/omnibox/browser/actions/omnibox_pedal_provider.h"
+#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/omnibox_proto/entity_info.pb.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+using omnibox::ActionInfo;
+using ActionType = omnibox::ActionInfo_ActionType;
+
+namespace {
+class FakeOmniboxAction : public OmniboxAction {
+ public:
+  explicit FakeOmniboxAction(OmniboxActionId id)
+      : OmniboxAction(LabelStrings(u"", u"", u"", u""), GURL{}), id_(id) {}
+  OmniboxActionId ActionId() const override { return id_; }
+
+ private:
+  ~FakeOmniboxAction() override = default;
+  OmniboxActionId id_{};
+};
+
+// Note: can't use operator<<, because ActionType is a plain old enum.
+const char* ToString(ActionType type) {
+  switch (type) {
+    case omnibox::ActionInfo_ActionType_CALL:
+      return "Call";
+    case omnibox::ActionInfo_ActionType_DIRECTIONS:
+      return "Directions";
+    case omnibox::ActionInfo_ActionType_REVIEWS:
+      return "Reviews";
+    case omnibox::ActionInfo_ActionType_WEBSITE:
+      return "Website";
+    default:
+      NOTREACHED();
+  }
+}
+}  // namespace
+
+class OmniboxActionInSuggestTest : public testing::Test {
+ public:
+  OmniboxActionInSuggestTest();
+
+ protected:
+  MockAutocompleteProviderClient client_;
+  OmniboxAction::ExecutionContext context_;
+};
+
+OmniboxActionInSuggestTest::OmniboxActionInSuggestTest()
+    : context_(client_,
+               OmniboxAction::ExecutionContext::OpenUrlCallback(),
+               base::TimeTicks(),
+               WindowOpenDisposition::IGNORE_ACTION) {}
+
+TEST_F(OmniboxActionInSuggestTest, CheckLabelsArePresentForKnownTypes) {
+  struct {
+    ActionType action_type;
+    int want_hint;
+    int want_contents;
+    int want_accessibility_focus_hint;
+    int want_accessibility_activate_hint;
+  } test_cases[] = {{
+                        omnibox::ActionInfo_ActionType_CALL,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_HINT,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_CONTENTS,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST_SUFFIX,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST,
+                    },
+                    {
+                        omnibox::ActionInfo_ActionType_DIRECTIONS,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_HINT,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_CONTENTS,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST_SUFFIX,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST,
+                    },
+                    {
+                        omnibox::ActionInfo_ActionType_REVIEWS,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_HINT,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_CONTENTS,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST_SUFFIX,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST,
+                    },
+                    {
+                        omnibox::ActionInfo_ActionType_WEBSITE,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_HINT,
+                        IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_CONTENTS,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST_SUFFIX,
+                        IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST,
+                    }};
+
+  for (const auto& test_case : test_cases) {
+    ActionInfo action_info;
+    action_info.set_action_type(test_case.action_type);
+
+    auto action =
+        base::MakeRefCounted<OmniboxActionInSuggest>(std::move(action_info));
+    EXPECT_EQ(OmniboxActionId::ACTION_IN_SUGGEST, action->ActionId())
+        << "while evaluatin action " << ToString(test_case.action_type);
+    EXPECT_EQ(test_case.action_type, action->Type());
+
+    const auto& labels = action->GetLabelStrings();
+    EXPECT_EQ(labels.hint, l10n_util::GetStringUTF16(test_case.want_hint))
+        << "while evaluatin action " << ToString(test_case.action_type);
+    EXPECT_EQ(labels.suggestion_contents,
+              l10n_util::GetStringUTF16(test_case.want_contents))
+        << "while evaluatin action " << ToString(test_case.action_type);
+    EXPECT_EQ(
+        labels.accessibility_suffix,
+        l10n_util::GetStringUTF16(test_case.want_accessibility_focus_hint))
+        << "while evaluatin action " << ToString(test_case.action_type);
+    EXPECT_EQ(
+        labels.accessibility_hint,
+        l10n_util::GetStringUTF16(test_case.want_accessibility_activate_hint))
+        << "while evaluatin action " << ToString(test_case.action_type);
+  }
+}
+
+TEST_F(OmniboxActionInSuggestTest, ConversionFromAction) {
+  const ActionType test_cases[]{omnibox::ActionInfo_ActionType_CALL,
+                                omnibox::ActionInfo_ActionType_DIRECTIONS,
+                                omnibox::ActionInfo_ActionType_REVIEWS,
+                                omnibox::ActionInfo_ActionType_WEBSITE};
+
+  for (auto test_case : test_cases) {
+    ActionInfo action_info;
+    action_info.set_action_type(test_case);
+
+    scoped_refptr<OmniboxAction> upcasted_action =
+        base::MakeRefCounted<OmniboxActionInSuggest>(std::move(action_info));
+
+    auto* downcasted_action =
+        OmniboxActionInSuggest::FromAction(upcasted_action.get());
+
+    EXPECT_EQ(upcasted_action.get(), downcasted_action)
+        << "while evaluatin action " << ToString(test_case);
+    EXPECT_EQ(test_case, downcasted_action->Type())
+        << "while evaluatin action " << ToString(test_case);
+  }
+}
+
+TEST_F(OmniboxActionInSuggestTest, ConversionFromNonAction) {
+  const OmniboxActionId test_cases[]{OmniboxActionId::HISTORY_CLUSTERS,
+                                     OmniboxActionId::PEDAL,
+                                     OmniboxActionId::TAB_SWITCH};
+
+  for (auto test_case : test_cases) {
+    auto fake_action = base::MakeRefCounted<FakeOmniboxAction>(test_case);
+    EXPECT_EQ(nullptr, OmniboxActionInSuggest::FromAction(fake_action.get()));
+  }
+}
+
+TEST_F(OmniboxActionInSuggestTest, AllDeclaredActionTypesAreProperlyReflected) {
+  // This test verifies that we're not quietly migrating new action types, and
+  // failing to recognize the need for appropriate coverage, both in terms of
+  // labels (hints, accessibility) but also UMA metrics.
+  for (int type = ActionInfo::ActionType_MIN;
+       type <= ActionInfo::ActionType_MAX; type++) {
+    if (omnibox::ActionInfo_ActionType_IsValid(type)) {
+      ActionInfo action_info;
+      action_info.set_action_type(ActionType(type));
+
+      // This is a valid action type. Object MUST build.
+      auto action =
+          base::MakeRefCounted<OmniboxActionInSuggest>(std::move(action_info));
+      // This is a valid action type. Object MUST be able to report metrics.
+      {
+        base::HistogramTester histograms;
+        action->RecordActionShown(1, false);
+        // NO actions report to the 'Unknown' bucket.
+        histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Shown", 0, 0);
+        histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Used", 0, 0);
+        histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Shown", 1);
+        histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Used", 0);
+      }
+
+      {
+        base::HistogramTester histograms;
+        action->RecordActionShown(1, true);
+        // NO actions report to the 'Unknown' bucket.
+        histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Shown", 0, 0);
+        histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Used", 0, 0);
+        histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Shown", 1);
+        histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Used", 1);
+      }
+    }
+  }
+}
+
+TEST_F(OmniboxActionInSuggestTest, HistogramsRecording) {
+  enum class UmaTypeForTest {
+    kUnknown = 0,
+    kCall,
+    kDirections,
+    kWebsite,
+    kReviews,
+  };
+
+  // Correlation between ActionType and UMA-recorded bucket.
+  const std::pair<ActionType, UmaTypeForTest> test_cases[]{
+      {omnibox::ActionInfo_ActionType_CALL, UmaTypeForTest::kCall},
+      {omnibox::ActionInfo_ActionType_DIRECTIONS, UmaTypeForTest::kDirections},
+      {omnibox::ActionInfo_ActionType_REVIEWS, UmaTypeForTest::kReviews},
+      {omnibox::ActionInfo_ActionType_WEBSITE, UmaTypeForTest::kWebsite},
+  };
+
+  for (const auto& test_case : test_cases) {
+    ActionInfo action_info;
+    action_info.set_action_type(test_case.first);
+    scoped_refptr<OmniboxAction> action =
+        base::MakeRefCounted<OmniboxActionInSuggest>(std::move(action_info));
+
+    {
+      // Just show.
+      base::HistogramTester histograms;
+      action->RecordActionShown(1, false);
+      histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Shown",
+                                   test_case.second, 1);
+      histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Used",
+                                   test_case.second, 0);
+      histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Shown", 1);
+      histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Used", 0);
+    }
+
+    {
+      // Show and execute.
+      base::HistogramTester histograms;
+      action->RecordActionShown(1, true);
+      histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Shown",
+                                   test_case.second, 1);
+      histograms.ExpectBucketCount("Omnibox.ActionInSuggest.Used",
+                                   test_case.second, 1);
+      histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Shown", 1);
+      histograms.ExpectTotalCount("Omnibox.ActionInSuggest.Used", 1);
+    }
+  }
+}
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionsMetrics.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/OmniboxMetrics.java
similarity index 88%
rename from chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionsMetrics.java
rename to components/omnibox/browser/android/java/src/org/chromium/components/omnibox/OmniboxMetrics.java
index 502c722..be743ff2 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionsMetrics.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/OmniboxMetrics.java
@@ -2,7 +2,7 @@
 // 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.omnibox.suggestions;
+package org.chromium.components.omnibox;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -14,14 +14,15 @@
 import org.chromium.components.omnibox.EntityInfoProto.ActionInfo.ActionType;
 import org.chromium.components.omnibox.action.ActionInSuggestUmaType;
 import org.chromium.components.omnibox.action.OmniboxPedalType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * This class collects a variety of different Suggestions related metrics.
+ * This class collects a variety of different Omnibox related metrics.
  */
-public class SuggestionsMetrics {
+public class OmniboxMetrics {
     /**
      * Maximum number of suggest tile types we want to record.
      * Anything beyond this will be reported in the overflow bucket.
@@ -32,20 +33,20 @@
      * Duration between the request for suggestions and the time the first (synchronous) reply is
      * converted to the UI model.
      */
-    static final String HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST =
+    public static final String HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST =
             "Android.Omnibox.SuggestionList.RequestToUiModel.First";
     /**
      * Duration between the request for suggestions and the time the last (asynchronous) reply is
      * converted to the UI model.
      */
-    static final String HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST =
+    public static final String HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST =
             "Android.Omnibox.SuggestionList.RequestToUiModel.Last";
 
     @IntDef({RefineActionUsage.NOT_USED, RefineActionUsage.SEARCH_WITH_ZERO_PREFIX,
             RefineActionUsage.SEARCH_WITH_PREFIX, RefineActionUsage.SEARCH_WITH_BOTH,
             RefineActionUsage.COUNT})
     @Retention(RetentionPolicy.SOURCE)
-    @interface RefineActionUsage {
+    public @interface RefineActionUsage {
         int NOT_USED = 0; // User did not interact with Refine button.
         int SEARCH_WITH_ZERO_PREFIX = 1; // User interacted with Refine button in zero-prefix mode.
         int SEARCH_WITH_PREFIX = 2; // User interacted with Refine button in non-zero-prefix mode.
@@ -72,14 +73,14 @@
     /**
      * Record how long the Suggestion List needed to layout its content and children.
      */
-    static TimingMetric recordSuggestionListLayoutTime() {
+    public static TimingMetric recordSuggestionListLayoutTime() {
         return TimingMetric.shortThreadTime("Android.Omnibox.SuggestionList.LayoutTime2");
     }
 
     /**
      * Record how long the Suggestion List needed to measure its content and children.
      */
-    static TimingMetric recordSuggestionListMeasureTime() {
+    public static TimingMetric recordSuggestionListMeasureTime() {
         return TimingMetric.shortThreadTime("Android.Omnibox.SuggestionList.MeasureTime2");
     }
 
@@ -87,7 +88,7 @@
      * Record the amount of time needed to create a new suggestion view.
      * The type of view is intentionally ignored for this call.
      */
-    static TimingMetric recordSuggestionViewCreateTime() {
+    public static TimingMetric recordSuggestionViewCreateTime() {
         return TimingMetric.shortThreadTime("Android.Omnibox.SuggestionView.CreateTime2");
     }
 
@@ -99,7 +100,7 @@
      * @param viewsReused Ratio of views re-used to total views bound. Effectively captures the
      *         efficiency of view recycling.
      */
-    static void recordSuggestionViewReuseStats(int viewsCreated, int viewsReused) {
+    public static void recordSuggestionViewReuseStats(int viewsCreated, int viewsReused) {
         RecordHistogram.recordCount100Histogram(
                 "Android.Omnibox.SuggestionView.SessionViewsCreated", viewsCreated);
         RecordHistogram.recordCount100Histogram(
@@ -114,7 +115,7 @@
      *
      * @param type The type of view that needed to be recreated.
      */
-    static void recordSuggestionsViewCreatedType(@OmniboxSuggestionUiType int type) {
+    public static void recordSuggestionsViewCreatedType(@OmniboxSuggestionUiType int type) {
         RecordHistogram.recordEnumeratedHistogram(
                 "Android.Omnibox.SuggestionView.CreatedType", type, OmniboxSuggestionUiType.COUNT);
     }
@@ -126,7 +127,7 @@
      *
      * @param type The type of view that was reused from pool.
      */
-    static void recordSuggestionsViewReusedType(@OmniboxSuggestionUiType int type) {
+    public static void recordSuggestionsViewReusedType(@OmniboxSuggestionUiType int type) {
         RecordHistogram.recordEnumeratedHistogram(
                 "Android.Omnibox.SuggestionView.ReusedType", type, OmniboxSuggestionUiType.COUNT);
     }
@@ -137,7 +138,7 @@
      *
      * @param focusResultedInNavigation Whether the user completed interaction with navigation.
      */
-    static void recordOmniboxFocusResultedInNavigation(boolean focusResultedInNavigation) {
+    public static void recordOmniboxFocusResultedInNavigation(boolean focusResultedInNavigation) {
         RecordHistogram.recordBooleanHistogram(
                 "Omnibox.FocusResultedInNavigation", focusResultedInNavigation);
     }
@@ -145,7 +146,7 @@
     /**
      * Record the length of time between when omnibox gets focused and when a omnibox match is open.
      */
-    static void recordFocusToOpenTime(long focusToOpenTimeInMillis) {
+    public static void recordFocusToOpenTime(long focusToOpenTimeInMillis) {
         RecordHistogram.recordMediumTimesHistogram(
                 "Omnibox.FocusToOpenTimeAnyPopupState3", focusToOpenTimeInMillis);
     }
@@ -155,7 +156,7 @@
      *
      * @param isFromCache Whether the suggestion selected by the User comes from suggestion cache.
      */
-    static void recordUsedSuggestionFromCache(boolean isFromCache) {
+    public static void recordUsedSuggestionFromCache(boolean isFromCache) {
         RecordHistogram.recordBooleanHistogram(
                 "Android.Omnibox.UsedSuggestionFromCache", isFromCache);
     }
@@ -168,7 +169,7 @@
      *
      * @param refineActionUsage Whether - and how Refine action button was used.
      */
-    static void recordRefineActionUsage(@RefineActionUsage int refineActionUsage) {
+    public static void recordRefineActionUsage(@RefineActionUsage int refineActionUsage) {
         RecordHistogram.recordEnumeratedHistogram(
                 "Android.Omnibox.RefineActionUsage", refineActionUsage, RefineActionUsage.COUNT);
     }
@@ -200,7 +201,7 @@
      * @param pageClass Page classification.
      * @param wasScrolled Whether the suggestions list was scrolled.
      */
-    static void recordSuggestionsListScrolled(int pageClass, boolean wasScrolled) {
+    public static void recordSuggestionsListScrolled(int pageClass, boolean wasScrolled) {
         RecordHistogram.recordBooleanHistogram(
                 histogramName("Android.Omnibox.SuggestionsListScrolled", pageClass), wasScrolled);
     }
@@ -226,8 +227,8 @@
      */
     public static void recordResumeJourneyClick(int position) {
         if (position < 0) return;
-        RecordHistogram.recordExactLinearHistogram("Omnibox.SuggestionUsed.ResumeJourney", position,
-                SuggestionsMetrics.MAX_AUTOCOMPLETE_POSITION);
+        RecordHistogram.recordExactLinearHistogram(
+                "Omnibox.SuggestionUsed.ResumeJourney", position, MAX_AUTOCOMPLETE_POSITION);
     }
 
     /**
@@ -236,8 +237,8 @@
      */
     public static void recordResumeJourneyShown(int position) {
         if (position < 0) return;
-        RecordHistogram.recordEnumeratedHistogram("Omnibox.ResumeJourneyShown", position,
-                SuggestionsMetrics.MAX_AUTOCOMPLETE_POSITION);
+        RecordHistogram.recordEnumeratedHistogram(
+                "Omnibox.ResumeJourneyShown", position, MAX_AUTOCOMPLETE_POSITION);
     }
 
     /**
@@ -304,6 +305,8 @@
                 return ActionInSuggestUmaType.DIRECTIONS;
             case ActionType.WEBSITE_VALUE:
                 return ActionInSuggestUmaType.WEBSITE;
+            case ActionType.REVIEWS_VALUE:
+                return ActionInSuggestUmaType.REVIEWS;
             default:
                 return ActionInSuggestUmaType.UNKNOWN;
         }
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersAction.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersAction.java
index ace6a99..aee9a0577 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersAction.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersAction.java
@@ -31,6 +31,11 @@
         this.query = query;
     }
 
+    @Override
+    public void execute(@NonNull OmniboxActionDelegate delegate) {
+        delegate.openHistoryClustersPage(query);
+    }
+
     /**
      * Cast supplied OmniboxAction to HistoryClustersAction.
      * Requires the supplied input to be a valid instance of an HistoryClustersAction whose
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersActionUnitTest.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersActionUnitTest.java
index 1467c86..0879665 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersActionUnitTest.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/HistoryClustersActionUnitTest.java
@@ -6,9 +6,15 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -19,6 +25,9 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class HistoryClustersActionUnitTest {
+    public @Rule MockitoRule mockitoRule = MockitoJUnit.rule();
+    private @Mock OmniboxActionDelegate mDelegate;
+
     @Test
     public void creation_usesExpectedIcon() {
         var action = new HistoryClustersAction("hint", "query");
@@ -62,4 +71,13 @@
     public void safeCasting_successWithHistoryClusters() {
         HistoryClustersAction.from(new HistoryClustersAction("hint", "query"));
     }
+
+    @Test
+    public void executeHistoryClusters() {
+        String testJourneyName = "example journey name";
+        var action = new HistoryClustersAction("hint", testJourneyName);
+        action.execute(mDelegate);
+        verify(mDelegate).openHistoryClustersPage(testJourneyName);
+        verifyNoMoreInteractions(mDelegate);
+    }
 }
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxAction.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxAction.java
index 654ac3f..e6c9b01 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxAction.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxAction.java
@@ -50,4 +50,11 @@
         this.hint = hint;
         this.icon = icon != null ? icon : DEFAULT_ICON;
     }
+
+    /**
+     * Execute the associated action.
+     *
+     * @param delegate delegate capable of routing and executing variety of action-specific tasks
+     */
+    public void execute(@NonNull OmniboxActionDelegate delegate){};
 }
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionDelegate.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionDelegate.java
new file mode 100644
index 0000000..ecb08b5
--- /dev/null
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionDelegate.java
@@ -0,0 +1,67 @@
+// 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.components.omnibox.action;
+
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.components.browser_ui.settings.SettingsLauncher.SettingsFragment;
+
+/**
+ * An interface for handling interactions for Omnibox Action Chips.
+ */
+public interface OmniboxActionDelegate {
+    /**
+     * Call this method when the pedal is clicked.
+     * TODO(crbug/1418077): move this to OmniboxAction.
+     *
+     * @param action the {@link OmniboxAction} whose action we want to execute.
+     */
+    void execute(OmniboxAction action);
+
+    /**
+     * Returns whether the user is currently browsing incognito.
+     */
+    boolean isIncognito();
+
+    /**
+     * Load the supplied URL in the current tab (if possible), or a new tab (otherwise).
+     *
+     * @param url the page URL to load
+     */
+    void loadPageInCurrentTab(String url);
+
+    /**
+     * Start the activity referenced by the supplied {@link android.content.Intent}.
+     * Decorates the intent with trusted intent extras when the intent references the browser.
+     *
+     * @param intent the intent describing the activity to be started
+     * @return whether operation was successful
+     */
+    boolean startActivity(@NonNull Intent intent);
+
+    /**
+     * Create a new incognito tab.
+     */
+    void openIncognitoTab();
+
+    /**
+     * Open Password Manager.
+     */
+    void openPasswordManager();
+
+    /**
+     * Open specific settings page.
+     */
+    void openSettingsPage(@SettingsFragment int fragment);
+
+    /**
+     * Open History Clusters page for a specific user query.
+     *
+     * @param query the query to access History Clusters for
+     */
+    void openHistoryClustersPage(String query);
+}
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggest.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggest.java
index 318efff..27b4880 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggest.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.components.omnibox.action;
 
+import android.content.Intent;
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
@@ -14,8 +15,11 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.components.omnibox.EntityInfoProto;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.components.omnibox.R;
 
+import java.net.URISyntaxException;
+
 /**
  * Omnibox action for showing the Action in Suggest UI.
  */
@@ -56,6 +60,87 @@
         return map;
     }
 
+    /**
+     * Execute an Intent associated with OmniboxActionInSuggest.
+     *
+     * TODO(crbug/1418077): pass the dependencies to constructor and define method in the interface.
+     *
+     * @param loadPageInCurrentTab loads the page in the current tab (if available), else new tab
+     * @param startActivity starts the activity described by supplied intent
+     */
+    @Override
+    public void execute(OmniboxActionDelegate delegate) {
+        var actionType = actionInfo.getActionType();
+        boolean actionStarted = false;
+        boolean isIncognito = delegate.isIncognito();
+        Intent intent = null;
+
+        try {
+            intent = Intent.parseUri(actionInfo.getActionUri(), Intent.URI_INTENT_SCHEME);
+        } catch (URISyntaxException e) {
+            // Never happens. http://b/279756377.
+        }
+
+        switch (actionType) {
+            case WEBSITE:
+                // Rather than invoking an intent that opens a new tab, load the page in the
+                // current tab.
+                delegate.loadPageInCurrentTab(intent.getDataString());
+                actionStarted = true;
+                break;
+
+            case REVIEWS:
+                assert false : "Pending implementation";
+                break;
+
+            case CALL:
+                // Don't call directly. Use `DIAL` instead to let the user decide.
+                // Note also that ACTION_CALL requires a dedicated permission.
+                intent.setAction(Intent.ACTION_DIAL);
+                // Start dialer even if the user is in incognito mode. The intent only pre-dials
+                // the phone number without ever making the call. This gives the user the chance
+                // to abandon before making a call.
+                actionStarted = delegate.startActivity(intent);
+                break;
+
+            case DIRECTIONS:
+                // Open directions in maps only if maps are installed and the incognito mode is
+                // not engaged. In all other cases, redirect the action to Browser.
+                if (!isIncognito) {
+                    actionStarted = delegate.startActivity(intent);
+                }
+                break;
+
+                // No `default` to capture new variants.
+        }
+
+        // Record intent started only if it was sent.
+        if (actionStarted) {
+            OmniboxMetrics.recordActionInSuggestIntentResult(
+                    OmniboxMetrics.ActionInSuggestIntentResult.SUCCESS);
+        } else {
+            // At this point we know that we were either unable to launch the target activity
+            // or the user is browsing incognito, where we suppress some actions.
+            // We may still be able to handle the corresponding action inside the browser.
+            if (!isIncognito) {
+                OmniboxMetrics.recordActionInSuggestIntentResult(
+                        OmniboxMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND);
+            }
+
+            switch (actionType) {
+                case DIRECTIONS:
+                    delegate.loadPageInCurrentTab(intent.getDataString());
+                    break;
+
+                case CALL:
+                case REVIEWS:
+                case WEBSITE:
+                    // Give up. Don't add the `default` clause though, capture missed variants.
+                    break;
+            }
+        }
+    }
+
     @CalledByNative
     @VisibleForTesting
     public static @Nullable OmniboxActionInSuggest build(
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggestUnitTest.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggestUnitTest.java
index 5dfd721..7cf63cf 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggestUnitTest.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxActionInSuggestUnitTest.java
@@ -9,13 +9,30 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.content.Intent;
+import android.net.Uri;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.omnibox.EntityInfoProto;
+import org.chromium.components.omnibox.OmniboxMetrics;
 
 import java.util.List;
 
@@ -32,6 +49,11 @@
     private static final EntityInfoProto.ActionInfo EMPTY_INFO =
             EntityInfoProto.ActionInfo.getDefaultInstance();
 
+    public @Rule MockitoRule mockitoRule = MockitoJUnit.rule();
+    private @Mock OmniboxActionDelegate mDelegate;
+    private @Captor ArgumentCaptor<Intent> mIntentCaptor;
+    private @Captor ArgumentCaptor<String> mUrlCaptor;
+
     @Test
     public void creation_usesCustomIconForKnownActionTypes() {
         for (var kesemActionType : sKnownActionTypes) {
@@ -99,4 +121,136 @@
     public void safeCasting_successWithHistoryClusters() {
         OmniboxActionInSuggest.from(new OmniboxActionInSuggest("hint", EMPTY_INFO));
     }
+
+    /**
+     * Create Action in Suggest with a supplied definition.
+     */
+    private OmniboxAction buildActionInSuggest(
+            EntityInfoProto.ActionInfo.ActionType type, Intent intent) {
+        var uri = intent.toUri(Intent.URI_INTENT_SCHEME);
+        var action = EntityInfoProto.ActionInfo.newBuilder()
+                             .setActionType(type)
+                             .setActionUri(uri)
+                             .build();
+
+        return new OmniboxActionInSuggest("wink", action);
+    }
+
+    @Test
+    public void executeActionInSuggest_executeDirectionsWithMaps() {
+        doReturn(false).when(mDelegate).isIncognito();
+        doReturn(true).when(mDelegate).startActivity(any());
+
+        buildActionInSuggest(
+                EntityInfoProto.ActionInfo.ActionType.DIRECTIONS, new Intent("Magic Intent Action"))
+                .execute(mDelegate);
+
+        verify(mDelegate, times(1)).isIncognito();
+        verify(mDelegate, times(1)).startActivity(mIntentCaptor.capture());
+        var intent = mIntentCaptor.getValue();
+
+        assertEquals("Magic Intent Action", intent.getAction());
+
+        assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "Android.Omnibox.ActionInSuggest.IntentResult",
+                        OmniboxMetrics.ActionInSuggestIntentResult.SUCCESS));
+        verifyNoMoreInteractions(mDelegate);
+    }
+
+    @Test
+    public void executeActionInSuggest_executeDirectionsInBrowserForIncognitoMode() {
+        doReturn(true).when(mDelegate).isIncognito();
+
+        var intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
+
+        buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.DIRECTIONS, intent)
+                .execute(mDelegate);
+
+        verify(mDelegate, times(1)).isIncognito();
+
+        // Should not be recorded.
+        assertEquals(0,
+                RecordHistogram.getHistogramTotalCountForTesting(
+                        "Android.Omnibox.ActionInSuggest.IntentResult"));
+
+        verify(mDelegate, times(1)).loadPageInCurrentTab(mUrlCaptor.capture());
+
+        var url = mUrlCaptor.getValue();
+        assertNotNull(url);
+        assertEquals(UrlConstants.CHROME_DINO_URL, url);
+        verifyNoMoreInteractions(mDelegate);
+    }
+
+    @Test
+    public void executeActionInSuggest_redirectDirectionsActionToLocalTabIfAvailable() {
+        doReturn(false).when(mDelegate).isIncognito();
+        doReturn(false).when(mDelegate).startActivity(any());
+
+        var intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
+
+        buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.DIRECTIONS, intent)
+                .execute(mDelegate);
+
+        verify(mDelegate, times(1)).isIncognito();
+
+        assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "Android.Omnibox.ActionInSuggest.IntentResult",
+                        OmniboxMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND));
+
+        verify(mDelegate, times(1)).loadPageInCurrentTab(mUrlCaptor.capture());
+        verify(mDelegate, times(1)).startActivity(any());
+
+        var url = mUrlCaptor.getValue();
+        assertNotNull(url);
+        assertEquals(UrlConstants.CHROME_DINO_URL, url);
+        verifyNoMoreInteractions(mDelegate);
+    }
+
+    @Test
+    public void executeActionInSuggest_executeCallActionWithDialer() {
+        doReturn(false).when(mDelegate).isIncognito();
+        doReturn(true).when(mDelegate).startActivity(any());
+
+        buildActionInSuggest(
+                EntityInfoProto.ActionInfo.ActionType.CALL, new Intent(Intent.ACTION_CALL))
+                .execute(mDelegate);
+
+        verify(mDelegate, times(1)).isIncognito();
+        verify(mDelegate, times(1)).startActivity(mIntentCaptor.capture());
+        var intent = mIntentCaptor.getValue();
+
+        // OBSERVE: We rewrite ACTION_CALL with ACTION_DIAL, which does not carry high permission
+        // requirements.
+        assertEquals(Intent.ACTION_DIAL, intent.getAction());
+
+        assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "Android.Omnibox.ActionInSuggest.IntentResult",
+                        OmniboxMetrics.ActionInSuggestIntentResult.SUCCESS));
+        verifyNoMoreInteractions(mDelegate);
+    }
+
+    @Test
+    public void executeActionInSuggest_dontRedirectCallActionToLocalTab() {
+        doReturn(false).when(mDelegate).isIncognito();
+        doReturn(false).when(mDelegate).startActivity(any());
+
+        var intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(Uri.parse(UrlConstants.CHROME_DINO_URL));
+
+        buildActionInSuggest(EntityInfoProto.ActionInfo.ActionType.CALL, intent).execute(mDelegate);
+
+        verify(mDelegate, times(1)).isIncognito();
+        verify(mDelegate, times(1)).startActivity(any());
+
+        assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "Android.Omnibox.ActionInSuggest.IntentResult",
+                        OmniboxMetrics.ActionInSuggestIntentResult.ACTIVITY_NOT_FOUND));
+        verifyNoMoreInteractions(mDelegate);
+    }
 }
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedal.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedal.java
index 8ee82b0..8533e6b 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedal.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedal.java
@@ -8,6 +8,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.components.browser_ui.settings.SettingsLauncher.SettingsFragment;
+import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.components.omnibox.OmniboxMetrics;
 import org.chromium.components.omnibox.R;
 
 /**
@@ -27,6 +30,43 @@
         this.pedalId = pedalId;
     }
 
+    @Override
+    public void execute(@NonNull OmniboxActionDelegate delegate) {
+        switch (pedalId) {
+            case OmniboxPedalType.MANAGE_CHROME_SETTINGS:
+                delegate.openSettingsPage(SettingsFragment.MAIN);
+                break;
+            case OmniboxPedalType.CLEAR_BROWSING_DATA:
+                delegate.openSettingsPage(SettingsFragment.CLEAR_BROWSING_DATA);
+                break;
+            case OmniboxPedalType.UPDATE_CREDIT_CARD:
+                delegate.openSettingsPage(SettingsFragment.PAYMENT_METHODS);
+                break;
+            case OmniboxPedalType.RUN_CHROME_SAFETY_CHECK:
+                delegate.openSettingsPage(SettingsFragment.SAFETY_CHECK);
+                break;
+            case OmniboxPedalType.MANAGE_SITE_SETTINGS:
+                delegate.openSettingsPage(SettingsFragment.SITE);
+                break;
+            case OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY:
+                delegate.openSettingsPage(SettingsFragment.ACCESSIBILITY);
+                break;
+            case OmniboxPedalType.VIEW_CHROME_HISTORY:
+                delegate.loadPageInCurrentTab(UrlConstants.HISTORY_URL);
+                break;
+            case OmniboxPedalType.PLAY_CHROME_DINO_GAME:
+                delegate.loadPageInCurrentTab(UrlConstants.CHROME_DINO_URL);
+                break;
+            case OmniboxPedalType.MANAGE_PASSWORDS:
+                delegate.openPasswordManager();
+                break;
+            case OmniboxPedalType.LAUNCH_INCOGNITO:
+                delegate.openIncognitoTab();
+                break;
+        }
+        OmniboxMetrics.recordPedalUsed(pedalId);
+    }
+
     /**
      * Cast supplied OmniboxAction to OmniboxPedal.
      * Requires the supplied input to be a valid instance of an OmniboxPedal whose
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedalUnitTest.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedalUnitTest.java
index fc2414c..93065d1 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedalUnitTest.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/action/OmniboxPedalUnitTest.java
@@ -6,12 +6,22 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.components.browser_ui.settings.SettingsLauncher.SettingsFragment;
+import org.chromium.components.embedder_support.util.UrlConstants;
 
 import java.util.List;
 
@@ -21,6 +31,8 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class OmniboxPedalUnitTest {
+    public @Rule MockitoRule mockitoRule = MockitoJUnit.rule();
+    private @Mock OmniboxActionDelegate mDelegate;
     private static List<Integer> sPedalsWithCustomIcons =
             List.of(OmniboxPedalType.PLAY_CHROME_DINO_GAME);
 
@@ -65,4 +77,98 @@
     public void safeCasting_successWithPedal() {
         OmniboxPedal.from(new OmniboxPedal("hint", OmniboxPedalType.NONE));
     }
+
+    /**
+     * Verify that a histogram recording the use of particular type of OmniboxPedal has been
+     * recorded.
+     *
+     * @param type The type of Pedal to check for.
+     */
+    private void checkOmniboxPedalUsageRecorded(@OmniboxPedalType int type) {
+        assertEquals(1,
+                RecordHistogram.getHistogramValueCountForTesting(
+                        "Omnibox.SuggestionUsed.Pedal", type));
+        assertEquals(1,
+                RecordHistogram.getHistogramTotalCountForTesting("Omnibox.SuggestionUsed.Pedal"));
+    }
+
+    @Test
+    public void executePedal_manageChromeSettings() {
+        new OmniboxPedal("hint", OmniboxPedalType.MANAGE_CHROME_SETTINGS).execute(mDelegate);
+        verify(mDelegate, times(1)).openSettingsPage(SettingsFragment.MAIN);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_CHROME_SETTINGS);
+    }
+
+    @Test
+    public void executePedal_clearBrowsingData() {
+        new OmniboxPedal("hint", OmniboxPedalType.CLEAR_BROWSING_DATA).execute(mDelegate);
+        verify(mDelegate, times(1)).openSettingsPage(SettingsFragment.CLEAR_BROWSING_DATA);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.CLEAR_BROWSING_DATA);
+    }
+
+    @Test
+    public void executePedal_managePasswords() {
+        new OmniboxPedal("hint", OmniboxPedalType.MANAGE_PASSWORDS).execute(mDelegate);
+        verify(mDelegate, times(1)).openPasswordManager();
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_PASSWORDS);
+    }
+
+    @Test
+    public void executePedal_updateCreditCard() {
+        new OmniboxPedal("hint", OmniboxPedalType.UPDATE_CREDIT_CARD).execute(mDelegate);
+        verify(mDelegate, times(1)).openSettingsPage(SettingsFragment.PAYMENT_METHODS);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.UPDATE_CREDIT_CARD);
+    }
+
+    @Test
+    public void executePedal_runChromeSafetyCheck() {
+        new OmniboxPedal("hint", OmniboxPedalType.RUN_CHROME_SAFETY_CHECK).execute(mDelegate);
+        verify(mDelegate, times(1)).openSettingsPage(SettingsFragment.SAFETY_CHECK);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.RUN_CHROME_SAFETY_CHECK);
+    }
+
+    @Test
+    public void executePedal_manageSiteSettings() {
+        new OmniboxPedal("hint", OmniboxPedalType.MANAGE_SITE_SETTINGS).execute(mDelegate);
+        verify(mDelegate, times(1)).openSettingsPage(SettingsFragment.SITE);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_SITE_SETTINGS);
+    }
+
+    @Test
+    public void executePedal_manageChromeAccessibility() {
+        new OmniboxPedal("hint", OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY).execute(mDelegate);
+        verify(mDelegate, times(1)).openSettingsPage(SettingsFragment.ACCESSIBILITY);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.MANAGE_CHROME_ACCESSIBILITY);
+    }
+
+    @Test
+    public void executePedal_launchIncognito() {
+        new OmniboxPedal("hint", OmniboxPedalType.LAUNCH_INCOGNITO).execute(mDelegate);
+        verify(mDelegate, times(1)).openIncognitoTab();
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.LAUNCH_INCOGNITO);
+    }
+
+    @Test
+    public void executePedal_viewChromeHistory() {
+        new OmniboxPedal("hint", OmniboxPedalType.VIEW_CHROME_HISTORY).execute(mDelegate);
+        verify(mDelegate, times(1)).loadPageInCurrentTab(UrlConstants.HISTORY_URL);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.VIEW_CHROME_HISTORY);
+    }
+
+    @Test
+    public void executePedal_playChromeDinoGame() {
+        new OmniboxPedal("hint", OmniboxPedalType.PLAY_CHROME_DINO_GAME).execute(mDelegate);
+        verify(mDelegate, times(1)).loadPageInCurrentTab(UrlConstants.CHROME_DINO_URL);
+        verifyNoMoreInteractions(mDelegate);
+        checkOmniboxPedalUsageRecorded(OmniboxPedalType.PLAY_CHROME_DINO_GAME);
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/suggestions/OmniboxSuggestionUiType.java
similarity index 96%
rename from chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java
rename to components/omnibox/browser/android/java/src/org/chromium/components/omnibox/suggestions/OmniboxSuggestionUiType.java
index df4852ac..05dadfc 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/suggestions/OmniboxSuggestionUiType.java
@@ -2,7 +2,7 @@
 // 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.omnibox.suggestions;
+package org.chromium.components.omnibox.suggestions;
 
 import androidx.annotation.IntDef;
 
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 3a73645..c826c61 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -1126,7 +1126,7 @@
 #if !BUILDFLAG(IS_IOS)
     // HistoryClusters is not enabled on iOS.
     AttachHistoryClustersActions(provider_client_->GetHistoryClustersService(),
-                                 provider_client_->GetPrefs(), result_);
+                                 result_);
 #endif
   }
   result_.TrimOmniboxActions();
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index e6facfa..53c95b2e 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -681,9 +681,11 @@
   for (size_t i = 0; i < max_index && pedals_found.size() < kMaxPedalCount;
        i++) {
     AutocompleteMatch& match = matches_[i];
-    // Skip matches that already have an `action` or are not suitable
-    // for actions.
-    if (!match.actions.empty() || !match.IsActionCompatible()) {
+    // Skip matches that already have a pedal or are not suitable for actions.
+    constexpr auto is_pedal = [](const auto& action) {
+      return action->ActionId() == OmniboxActionId::PEDAL;
+    };
+    if (match.GetActionWhere(is_pedal) || !match.IsActionCompatible()) {
       continue;
     }
 
diff --git a/components/omnibox/browser/autocomplete_result_unittest.cc b/components/omnibox/browser/autocomplete_result_unittest.cc
index ebfe3e3..eef8467 100644
--- a/components/omnibox/browser/autocomplete_result_unittest.cc
+++ b/components/omnibox/browser/autocomplete_result_unittest.cc
@@ -2318,6 +2318,27 @@
     const auto* pedal = OmniboxPedal::FromAction(action.get());
     return pedal && pedal->PedalId() == OmniboxPedalId::CLEAR_BROWSING_DATA;
   }));
+
+// Android & iOS avoid attaching tab-switch actions by design.
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+  // Include a tab-switch action, which is common and shouldn't prevent
+  // pedals from attaching to the same match. The first match has a URL
+  // that triggers tab-switch action attachment with this fake matcher.
+  static_cast<FakeTabMatcher&>(const_cast<TabMatcher&>(client.GetTabMatcher()))
+      .set_url_substring_match("clear-history");
+  result.match_at(0)->actions.clear();
+  result.match_at(0)->has_tab_match.reset();
+  result.ConvertOpenTabMatches(&client, &input);
+  EXPECT_EQ(result.match_at(0)->actions.size(), 1u);
+  EXPECT_EQ(result.match_at(0)->GetActionAt(0u)->ActionId(),
+            OmniboxActionId::TAB_SWITCH);
+  result.AttachPedalsToMatches(input, client);
+  EXPECT_EQ(result.match_at(0)->actions.size(), 2u);
+  ASSERT_NE(nullptr, result.match_at(0)->GetActionWhere([](const auto& action) {
+    const auto* pedal = OmniboxPedal::FromAction(action.get());
+    return pedal && pedal->PedalId() == OmniboxPedalId::CLEAR_BROWSING_DATA;
+  }));
+#endif
 }
 
 TEST_F(AutocompleteResultTest, DocumentSuggestionsCanMergeButNotToDefault) {
diff --git a/components/omnibox/browser/history_cluster_provider.cc b/components/omnibox/browser/history_cluster_provider.cc
index 5d1415a..2c6166d 100644
--- a/components/omnibox/browser/history_cluster_provider.cc
+++ b/components/omnibox/browser/history_cluster_provider.cc
@@ -65,8 +65,8 @@
   if (input.omit_asynchronous_matches())
     return;
 
-  if (!IsJourneysEnabledInOmnibox(client_->GetHistoryClustersService(),
-                                  client_->GetPrefs())) {
+  if (!client_->GetHistoryClustersService() ||
+      !client_->GetHistoryClustersService()->IsJourneysEnabled()) {
     return;
   }
 
diff --git a/components/omnibox/browser/history_cluster_provider_unittest.cc b/components/omnibox/browser/history_cluster_provider_unittest.cc
index dd451c3c..bbe790f 100644
--- a/components/omnibox/browser/history_cluster_provider_unittest.cc
+++ b/components/omnibox/browser/history_cluster_provider_unittest.cc
@@ -51,6 +51,13 @@
     history_service_ =
         history::CreateHistoryService(history_dir_.GetPath(), true);
 
+    autocomplete_provider_client_ =
+        std::make_unique<FakeAutocompleteProviderClient>();
+    static_cast<TestingPrefServiceSimple*>(
+        autocomplete_provider_client_->GetPrefs())
+        ->registry()
+        ->RegisterBooleanPref(history_clusters::prefs::kVisible, true);
+
     history_clusters_service_ =
         std::make_unique<history_clusters::HistoryClustersService>(
             "en-US", history_service_.get(),
@@ -58,22 +65,16 @@
             /*url_loader_factory=*/nullptr,
             /*engagement_score_provider=*/nullptr,
             /*template_url_service=*/nullptr,
-            /*optimization_guide_decider=*/nullptr, /*pref_service=*/nullptr);
+            /*optimization_guide_decider=*/nullptr,
+            autocomplete_provider_client_->GetPrefs());
 
     history_clusters_service_test_api_ =
         std::make_unique<history_clusters::HistoryClustersServiceTestApi>(
             history_clusters_service_.get(), history_service_.get());
     history_clusters_service_test_api_->SetAllKeywordsCache(
         {{u"keyword", {}}, {u"keyword2", {}}});
-
-    autocomplete_provider_client_ =
-        std::make_unique<FakeAutocompleteProviderClient>();
     autocomplete_provider_client_->set_history_clusters_service(
         history_clusters_service_.get());
-    static_cast<TestingPrefServiceSimple*>(
-        autocomplete_provider_client_->GetPrefs())
-        ->registry()
-        ->RegisterBooleanPref(history_clusters::prefs::kVisible, true);
 
     search_provider_ =
         new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
diff --git a/components/omnibox/browser/omnibox_client.h b/components/omnibox/browser/omnibox_client.h
index a72d853..588dc31 100644
--- a/components/omnibox/browser/omnibox_client.h
+++ b/components/omnibox/browser/omnibox_client.h
@@ -171,9 +171,6 @@
                              const AutocompleteResult& result,
                              bool has_focus) {}
 
-  // Called when input has been accepted.
-  virtual void OnInputAccepted(const AutocompleteMatch& match) {}
-
   // Called when the edit model is being reverted back to its unedited state.
   virtual void OnRevert() {}
 
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 9a96098..7b6c13d 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -2255,8 +2255,6 @@
     match.transition = ui::PAGE_TRANSITION_LINK;
   }
 
-  client_->OnInputAccepted(match);
-
   if (popup_view_) {
     OpenMatch(match, disposition, alternate_nav_url, std::u16string(),
               GetPopupSelection().line, match_selection_timestamp);
diff --git a/components/omnibox_strings.grdp b/components/omnibox_strings.grdp
index f7cd330d..00fa023 100644
--- a/components/omnibox_strings.grdp
+++ b/components/omnibox_strings.grdp
@@ -115,7 +115,7 @@
   </if>
 
 
-  <!-- Omnibox Actions, which are a generalization of Omnibox Pedals. -->
+  <!-- History Clusters Actions. -->
   <message name="IDS_OMNIBOX_ACTION_HISTORY_CLUSTERS_SEARCH_HINT" desc="The button text for the History Clusters omnibox action. Clicking this button takes users to a view of their browsing history related to the attached suggestion.">
     Resume your journey
   </message>
@@ -136,6 +136,30 @@
   <message name="IDS_ACC_OMNIBOX_ACTION_IN_SUGGEST" desc="Announcement when the Action in Suggest button is focused. Unlike Pedals or History Clusters, Actions come from the server and may encompass things like `Website` or `Call`, but are otherwise no different than, say, `Switch To Tab` on the attached screenshot.">
     Press Enter to activate this action.
   </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_HINT" desc="Label for Omnibox Action button that pre-dials the phone number." meaning="Length: 12em">
+    Call
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_CONTENTS" desc="The button hover tooltip text to describe action.">
+    Connect with a business by initiating a phone call.
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_HINT" desc="Label for Omnibox Action button that starts navigation to the POI." meaning="Length: 12em">
+    Directions
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_CONTENTS" desc="The button hover tooltip text to describe action.">
+    Get directions to your destination with turn-by-turn guidance using the navigation feature.
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_HINT" desc="Label for Omnibox Action button that opens Reviews page for the POI." meaning="Length: 12em">
+    Reviews
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_CONTENTS" desc="The button hover tooltip text to describe action.">
+    Read customer evaluations of products, services, or experiences to help inform your decisions with reviews.
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_HINT" desc="Label for Omnibox Action button that opens the POI website." meaning="Length: 12em">
+    Website
+  </message>
+  <message name="IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_CONTENTS" desc="The button hover tooltip text to describe action.">
+    Access the website to view information, resources, or services provided by the business.
+  </message>
 
   <!-- Accessibility labels for autocomplete match types.
        These are parameterized on the text being completed into the omnibox.
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_CONTENTS.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_CONTENTS.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_CONTENTS.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_HINT.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_HINT.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_CALL_HINT.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_CONTENTS.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_CONTENTS.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_CONTENTS.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_HINT.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_HINT.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_DIRECTIONS_HINT.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_CONTENTS.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_CONTENTS.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_CONTENTS.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_HINT.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_HINT.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_REVIEWS_HINT.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_CONTENTS.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_CONTENTS.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_CONTENTS.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_HINT.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_HINT.png.sha1
new file mode 100644
index 0000000..c21f425
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_ACTION_IN_SUGGEST_WEBSITE_HINT.png.sha1
@@ -0,0 +1 @@
+84818db637a203683ea423c9a79a8ab3b0e38a5c
\ No newline at end of file
diff --git a/components/page_image_service/image_service_unittest.cc b/components/page_image_service/image_service_unittest.cc
index 1daa782..9ed82cf 100644
--- a/components/page_image_service/image_service_unittest.cc
+++ b/components/page_image_service/image_service_unittest.cc
@@ -136,8 +136,7 @@
 
   test_sync_service_->GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kHistory));
+      /*types=*/{syncer::UserSelectableType::kHistory});
   test_sync_service_->FireStateChanged();
 
   EXPECT_TRUE(GetConsentToFetchImageAwaitResult(mojom::ClientId::Journeys));
@@ -154,8 +153,7 @@
       syncer::SyncService::TransportState::INITIALIZING);
   test_sync_service_->GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kHistory));
+      /*types=*/{syncer::UserSelectableType::kHistory});
   test_sync_service_->FireStateChanged();
 
   mojom::Options options;
diff --git a/components/password_manager/content/browser/content_password_manager_driver.cc b/components/password_manager/content/browser/content_password_manager_driver.cc
index b71d5476..694ea63 100644
--- a/components/password_manager/content/browser/content_password_manager_driver.cc
+++ b/components/password_manager/content/browser/content_password_manager_driver.cc
@@ -190,9 +190,10 @@
 }
 
 #if BUILDFLAG(IS_ANDROID)
-void ContentPasswordManagerDriver::TouchToFillClosed(
+void ContentPasswordManagerDriver::KeyboardReplacingSurfaceClosed(
     ShowVirtualKeyboard show_virtual_keyboard) {
-  GetPasswordAutofillAgent()->TouchToFillClosed(show_virtual_keyboard.value());
+  GetPasswordAutofillAgent()->KeyboardReplacingSurfaceClosed(
+      show_virtual_keyboard.value());
 }
 
 void ContentPasswordManagerDriver::TriggerFormSubmission() {
@@ -437,7 +438,7 @@
 }
 
 #if BUILDFLAG(IS_ANDROID)
-void ContentPasswordManagerDriver::ShowTouchToFill(
+void ContentPasswordManagerDriver::ShowKeyboardReplacingSurface(
     autofill::mojom::SubmissionReadinessState submission_readiness) {
   if (!password_manager::bad_message::CheckFrameNotPrerendering(
           render_frame_host_))
diff --git a/components/password_manager/content/browser/content_password_manager_driver.h b/components/password_manager/content/browser/content_password_manager_driver.h
index 8e1ca12..03d1ab7 100644
--- a/components/password_manager/content/browser/content_password_manager_driver.h
+++ b/components/password_manager/content/browser/content_password_manager_driver.h
@@ -74,7 +74,8 @@
   void FillIntoFocusedField(bool is_password,
                             const std::u16string& credential) override;
 #if BUILDFLAG(IS_ANDROID)
-  void TouchToFillClosed(ShowVirtualKeyboard show_virtual_keyboard) override;
+  void KeyboardReplacingSurfaceClosed(
+      ShowVirtualKeyboard show_virtual_keyboard) override;
   void TriggerFormSubmission() override;
 #endif
   void PreviewSuggestion(const std::u16string& username,
@@ -147,7 +148,7 @@
                                int options,
                                const gfx::RectF& bounds) override;
 #if BUILDFLAG(IS_ANDROID)
-  void ShowTouchToFill(
+  void ShowKeyboardReplacingSurface(
       autofill::mojom::SubmissionReadinessState submission_readiness) override;
 #endif
   void CheckSafeBrowsingReputation(const GURL& form_action,
diff --git a/components/password_manager/content/browser/content_password_manager_driver_unittest.cc b/components/password_manager/content/browser/content_password_manager_driver_unittest.cc
index 4929a80d..4fdf9ac6 100644
--- a/components/password_manager/content/browser/content_password_manager_driver_unittest.cc
+++ b/components/password_manager/content/browser/content_password_manager_driver_unittest.cc
@@ -96,7 +96,7 @@
               (bool, const std::u16string&),
               (override));
 #if BUILDFLAG(IS_ANDROID)
-  MOCK_METHOD(void, TouchToFillClosed, (bool), (override));
+  MOCK_METHOD(void, KeyboardReplacingSurfaceClosed, (bool), (override));
   MOCK_METHOD(void, TriggerFormSubmission, (), (override));
 #endif
   MOCK_METHOD(void,
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index c272170..2f7b970 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -239,8 +239,8 @@
     "statistics_table.h",
     "store_metrics_reporter.cc",
     "store_metrics_reporter.h",
-    "sync/password_model_type_controller.cc",
-    "sync/password_model_type_controller.h",
+    "sync/credential_model_type_controller.cc",
+    "sync/credential_model_type_controller.h",
     "sync/password_proto_utils.cc",
     "sync/password_proto_utils.h",
     "sync/password_sync_bridge.cc",
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index b7d520c..91cda07 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -1667,151 +1667,6 @@
   return DatabaseCleanupResult::kSuccess;
 }
 
-std::unique_ptr<syncer::MetadataBatch> LoginDatabase::GetAllSyncMetadata() {
-  TRACE_EVENT0("passwords", "LoginDatabase::GetAllSyncMetadata");
-  std::unique_ptr<syncer::MetadataBatch> metadata_batch =
-      GetAllSyncEntityMetadata();
-  if (metadata_batch == nullptr) {
-    return nullptr;
-  }
-
-  std::unique_ptr<sync_pb::ModelTypeState> model_type_state =
-      GetModelTypeState();
-  if (model_type_state == nullptr) {
-    return nullptr;
-  }
-
-  metadata_batch->SetModelTypeState(*model_type_state);
-  return metadata_batch;
-}
-
-void LoginDatabase::DeleteAllSyncMetadata() {
-  TRACE_EVENT0("passwords", "LoginDatabase::DeleteAllSyncMetadata");
-  bool had_unsynced_deletions = HasUnsyncedDeletions();
-  ClearAllSyncMetadata(&db_);
-  if (had_unsynced_deletions && deletions_have_synced_callback_) {
-    // Note: At this point we can't be fully sure whether the deletions actually
-    // reached the server yet. We might have sent a commit, but haven't received
-    // the commit confirmation. Let's be conservative and assume they haven't
-    // been successfully deleted.
-    deletions_have_synced_callback_.Run(/*success=*/false);
-  }
-}
-
-bool LoginDatabase::UpdateEntityMetadata(
-    syncer::ModelType model_type,
-    const std::string& storage_key,
-    const sync_pb::EntityMetadata& metadata) {
-  TRACE_EVENT0("passwords", "LoginDatabase::UpdateSyncMetadata");
-  DCHECK_EQ(model_type, syncer::PASSWORDS);
-
-  int storage_key_int = 0;
-  if (!base::StringToInt(storage_key, &storage_key_int)) {
-    DLOG(ERROR) << "Invalid storage key. Failed to convert the storage key to "
-                   "an integer.";
-    return false;
-  }
-
-  std::string encrypted_metadata;
-  if (!OSCrypt::EncryptString(metadata.SerializeAsString(),
-                              &encrypted_metadata)) {
-    DLOG(ERROR) << "Cannot encrypt the sync metadata";
-    return false;
-  }
-
-  sql::Statement s(
-      db_.GetCachedStatement(SQL_FROM_HERE,
-                             "INSERT OR REPLACE INTO sync_entities_metadata "
-                             "(storage_key, metadata) VALUES(?, ?)"));
-
-  s.BindInt(0, storage_key_int);
-  s.BindString(1, encrypted_metadata);
-
-  bool had_unsynced_deletions = HasUnsyncedDeletions();
-  bool result = s.Run();
-  if (result && had_unsynced_deletions && !HasUnsyncedDeletions() &&
-      deletions_have_synced_callback_) {
-    deletions_have_synced_callback_.Run(/*success=*/true);
-  }
-  return result;
-}
-
-bool LoginDatabase::ClearEntityMetadata(syncer::ModelType model_type,
-                                        const std::string& storage_key) {
-  TRACE_EVENT0("passwords", "LoginDatabase::ClearSyncMetadata");
-  DCHECK_EQ(model_type, syncer::PASSWORDS);
-
-  int storage_key_int = 0;
-  if (!base::StringToInt(storage_key, &storage_key_int)) {
-    DLOG(ERROR) << "Invalid storage key. Failed to convert the storage key to "
-                   "an integer.";
-    return false;
-  }
-
-  sql::Statement s(
-      db_.GetCachedStatement(SQL_FROM_HERE,
-                             "DELETE FROM sync_entities_metadata WHERE "
-                             "storage_key=?"));
-  s.BindInt(0, storage_key_int);
-
-  bool had_unsynced_deletions = HasUnsyncedDeletions();
-  bool result = s.Run();
-  if (result && had_unsynced_deletions && !HasUnsyncedDeletions() &&
-      deletions_have_synced_callback_) {
-    deletions_have_synced_callback_.Run(/*success=*/true);
-  }
-  return result;
-}
-
-bool LoginDatabase::UpdateModelTypeState(
-    syncer::ModelType model_type,
-    const sync_pb::ModelTypeState& model_type_state) {
-  TRACE_EVENT0("passwords", "LoginDatabase::UpdateModelTypeState");
-  DCHECK_EQ(model_type, syncer::PASSWORDS);
-
-  // Make sure only one row is left by storing it in the entry with id=1
-  // every time.
-  sql::Statement s(db_.GetCachedStatement(
-      SQL_FROM_HERE,
-      "INSERT OR REPLACE INTO sync_model_metadata (id, model_metadata) "
-      "VALUES(1, ?)"));
-  s.BindString(0, model_type_state.SerializeAsString());
-
-  return s.Run();
-}
-
-bool LoginDatabase::ClearModelTypeState(syncer::ModelType model_type) {
-  TRACE_EVENT0("passwords", "LoginDatabase::ClearModelTypeState");
-  DCHECK_EQ(model_type, syncer::PASSWORDS);
-
-  sql::Statement s(db_.GetCachedStatement(
-      SQL_FROM_HERE, "DELETE FROM sync_model_metadata WHERE id=1"));
-
-  return s.Run();
-}
-
-void LoginDatabase::SetDeletionsHaveSyncedCallback(
-    base::RepeatingCallback<void(bool)> callback) {
-  deletions_have_synced_callback_ = std::move(callback);
-}
-
-bool LoginDatabase::HasUnsyncedDeletions() {
-  TRACE_EVENT0("passwords", "LoginDatabase::HasUnsyncedDeletions");
-
-  std::unique_ptr<syncer::MetadataBatch> batch = GetAllSyncEntityMetadata();
-  if (!batch) {
-    return false;
-  }
-  for (const auto& metadata_entry : batch->GetAllMetadata()) {
-    // Note: No need for an explicit "is unsynced" check: Once the deletion is
-    // committed, the metadata entry is removed.
-    if (metadata_entry.second->is_deleted()) {
-      return true;
-    }
-  }
-  return false;
-}
-
 bool LoginDatabase::BeginTransaction() {
   TRACE_EVENT0("passwords", "LoginDatabase::BeginTransaction");
   return db_.BeginTransaction();
@@ -1827,37 +1682,19 @@
   return db_.CommitTransaction();
 }
 
-LoginDatabase::PrimaryKeyAndPassword LoginDatabase::GetPrimaryKeyAndPassword(
-    const PasswordForm& form) const {
-  DCHECK(!id_and_password_statement_.empty());
-  sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
-                                          id_and_password_statement_.c_str()));
-
-  s.BindString(0, form.url.spec());
-  s.BindString16(1, form.username_element);
-  s.BindString16(2, form.username_value);
-  s.BindString16(3, form.password_element);
-  s.BindString(4, form.signon_realm);
-
-  if (s.Step()) {
-    PrimaryKeyAndPassword result = {s.ColumnInt(0)};
-    s.ColumnBlobAsString(1, &result.encrypted_password);
-    if (DecryptedString(result.encrypted_password,
-                        &result.decrypted_password) !=
-        ENCRYPTION_RESULT_SUCCESS) {
-      result.decrypted_password.clear();
-    }
-    return result;
-  }
-  return {-1, std::string(), std::u16string()};
+LoginDatabase::SyncMetadataStore::SyncMetadataStore(sql::Database* db)
+    : db_(db) {
+  CHECK(db);
 }
 
+LoginDatabase::SyncMetadataStore::~SyncMetadataStore() = default;
+
 std::unique_ptr<syncer::MetadataBatch>
-LoginDatabase::GetAllSyncEntityMetadata() {
+LoginDatabase::SyncMetadataStore::GetAllSyncEntityMetadata() {
   auto metadata_batch = std::make_unique<syncer::MetadataBatch>();
-  sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
-                                          "SELECT storage_key, metadata FROM "
-                                          "sync_entities_metadata"));
+  sql::Statement s(db_->GetCachedStatement(SQL_FROM_HERE,
+                                           "SELECT storage_key, metadata FROM "
+                                           "sync_entities_metadata"));
 
   while (s.Step()) {
     int storage_key_int = s.ColumnInt(0);
@@ -1886,9 +1723,10 @@
   return metadata_batch;
 }
 
-std::unique_ptr<sync_pb::ModelTypeState> LoginDatabase::GetModelTypeState() {
+std::unique_ptr<sync_pb::ModelTypeState>
+LoginDatabase::SyncMetadataStore::GetModelTypeState() {
   auto state = std::make_unique<sync_pb::ModelTypeState>();
-  sql::Statement s(db_.GetCachedStatement(
+  sql::Statement s(db_->GetCachedStatement(
       SQL_FROM_HERE,
       "SELECT model_metadata FROM sync_model_metadata WHERE id=1"));
 
@@ -1907,6 +1745,179 @@
   return nullptr;
 }
 
+std::unique_ptr<syncer::MetadataBatch>
+LoginDatabase::SyncMetadataStore::GetAllSyncMetadata() {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::GetAllSyncMetadata");
+  std::unique_ptr<syncer::MetadataBatch> metadata_batch =
+      GetAllSyncEntityMetadata();
+  if (metadata_batch == nullptr) {
+    return nullptr;
+  }
+
+  std::unique_ptr<sync_pb::ModelTypeState> model_type_state =
+      GetModelTypeState();
+  if (model_type_state == nullptr) {
+    return nullptr;
+  }
+
+  metadata_batch->SetModelTypeState(*model_type_state);
+  return metadata_batch;
+}
+
+void LoginDatabase::SyncMetadataStore::DeleteAllSyncMetadata() {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::DeleteAllSyncMetadata");
+  bool had_unsynced_deletions = HasUnsyncedDeletions();
+  ClearAllSyncMetadata(db_);
+  if (had_unsynced_deletions && deletions_have_synced_callback_) {
+    // Note: At this point we can't be fully sure whether the deletions actually
+    // reached the server yet. We might have sent a commit, but haven't received
+    // the commit confirmation. Let's be conservative and assume they haven't
+    // been successfully deleted.
+    deletions_have_synced_callback_.Run(/*success=*/false);
+  }
+}
+
+bool LoginDatabase::SyncMetadataStore::UpdateEntityMetadata(
+    syncer::ModelType model_type,
+    const std::string& storage_key,
+    const sync_pb::EntityMetadata& metadata) {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::UpdateSyncMetadata");
+  DCHECK_EQ(model_type, syncer::PASSWORDS);
+
+  int storage_key_int = 0;
+  if (!base::StringToInt(storage_key, &storage_key_int)) {
+    DLOG(ERROR) << "Invalid storage key. Failed to convert the storage key to "
+                   "an integer.";
+    return false;
+  }
+
+  std::string encrypted_metadata;
+  if (!OSCrypt::EncryptString(metadata.SerializeAsString(),
+                              &encrypted_metadata)) {
+    DLOG(ERROR) << "Cannot encrypt the sync metadata";
+    return false;
+  }
+
+  sql::Statement s(
+      db_->GetCachedStatement(SQL_FROM_HERE,
+                              "INSERT OR REPLACE INTO sync_entities_metadata "
+                              "(storage_key, metadata) VALUES(?, ?)"));
+
+  s.BindInt(0, storage_key_int);
+  s.BindString(1, encrypted_metadata);
+
+  bool had_unsynced_deletions = HasUnsyncedDeletions();
+  bool result = s.Run();
+  if (result && had_unsynced_deletions && !HasUnsyncedDeletions() &&
+      deletions_have_synced_callback_) {
+    deletions_have_synced_callback_.Run(/*success=*/true);
+  }
+  return result;
+}
+
+bool LoginDatabase::SyncMetadataStore::ClearEntityMetadata(
+    syncer::ModelType model_type,
+    const std::string& storage_key) {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::ClearSyncMetadata");
+  DCHECK_EQ(model_type, syncer::PASSWORDS);
+
+  int storage_key_int = 0;
+  if (!base::StringToInt(storage_key, &storage_key_int)) {
+    DLOG(ERROR) << "Invalid storage key. Failed to convert the storage key to "
+                   "an integer.";
+    return false;
+  }
+
+  sql::Statement s(
+      db_->GetCachedStatement(SQL_FROM_HERE,
+                              "DELETE FROM sync_entities_metadata WHERE "
+                              "storage_key=?"));
+  s.BindInt(0, storage_key_int);
+
+  bool had_unsynced_deletions = HasUnsyncedDeletions();
+  bool result = s.Run();
+  if (result && had_unsynced_deletions && !HasUnsyncedDeletions() &&
+      deletions_have_synced_callback_) {
+    deletions_have_synced_callback_.Run(/*success=*/true);
+  }
+  return result;
+}
+
+bool LoginDatabase::SyncMetadataStore::UpdateModelTypeState(
+    syncer::ModelType model_type,
+    const sync_pb::ModelTypeState& model_type_state) {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::UpdateModelTypeState");
+  DCHECK_EQ(model_type, syncer::PASSWORDS);
+
+  // Make sure only one row is left by storing it in the entry with id=1
+  // every time.
+  sql::Statement s(db_->GetCachedStatement(
+      SQL_FROM_HERE,
+      "INSERT OR REPLACE INTO sync_model_metadata (id, model_metadata) "
+      "VALUES(1, ?)"));
+  s.BindString(0, model_type_state.SerializeAsString());
+
+  return s.Run();
+}
+
+bool LoginDatabase::SyncMetadataStore::ClearModelTypeState(
+    syncer::ModelType model_type) {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::ClearModelTypeState");
+  DCHECK_EQ(model_type, syncer::PASSWORDS);
+
+  sql::Statement s(db_->GetCachedStatement(
+      SQL_FROM_HERE, "DELETE FROM sync_model_metadata WHERE id=1"));
+
+  return s.Run();
+}
+
+void LoginDatabase::SyncMetadataStore::SetDeletionsHaveSyncedCallback(
+    base::RepeatingCallback<void(bool)> callback) {
+  deletions_have_synced_callback_ = std::move(callback);
+}
+
+bool LoginDatabase::SyncMetadataStore::HasUnsyncedDeletions() {
+  TRACE_EVENT0("passwords", "SyncMetadataStore::HasUnsyncedDeletions");
+
+  std::unique_ptr<syncer::MetadataBatch> batch = GetAllSyncEntityMetadata();
+  if (!batch) {
+    return false;
+  }
+  for (const auto& metadata_entry : batch->GetAllMetadata()) {
+    // Note: No need for an explicit "is unsynced" check: Once the deletion is
+    // committed, the metadata entry is removed.
+    if (metadata_entry.second->is_deleted()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+LoginDatabase::PrimaryKeyAndPassword LoginDatabase::GetPrimaryKeyAndPassword(
+    const PasswordForm& form) const {
+  DCHECK(!id_and_password_statement_.empty());
+  sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
+                                          id_and_password_statement_.c_str()));
+
+  s.BindString(0, form.url.spec());
+  s.BindString16(1, form.username_element);
+  s.BindString16(2, form.username_value);
+  s.BindString16(3, form.password_element);
+  s.BindString(4, form.signon_realm);
+
+  if (s.Step()) {
+    PrimaryKeyAndPassword result = {s.ColumnInt(0)};
+    s.ColumnBlobAsString(1, &result.encrypted_password);
+    if (DecryptedString(result.encrypted_password,
+                        &result.decrypted_password) !=
+        ENCRYPTION_RESULT_SUCCESS) {
+      result.decrypted_password.clear();
+    }
+    return result;
+  }
+  return {-1, std::string(), std::u16string()};
+}
+
 FormRetrievalResult LoginDatabase::StatementToForms(
     sql::Statement* statement,
     const PasswordFormDigest* matched_form,
diff --git a/components/password_manager/core/browser/login_database.h b/components/password_manager/core/browser/login_database.h
index 22fa064..f23d3c2 100644
--- a/components/password_manager/core/browser/login_database.h
+++ b/components/password_manager/core/browser/login_database.h
@@ -44,14 +44,14 @@
 // Interface to the database storage of login information, intended as a helper
 // for PasswordStore on platforms that need internal storage of some or all of
 // the login information.
-class LoginDatabase : public PasswordStoreSync::MetadataStore {
+class LoginDatabase {
  public:
   LoginDatabase(const base::FilePath& db_path, IsAccountStore is_account_store);
 
   LoginDatabase(const LoginDatabase&) = delete;
   LoginDatabase& operator=(const LoginDatabase&) = delete;
 
-  ~LoginDatabase() override;
+  virtual ~LoginDatabase();
 
   // Returns whether this is the profile-scoped or the account-scoped storage:
   // true:  Gaia-account-scoped store, which is used for signed-in but not
@@ -169,22 +169,6 @@
   // removed from the database, returns ITEM_FAILURE.
   DatabaseCleanupResult DeleteUndecryptableLogins();
 
-  // PasswordStoreSync::MetadataStore implementation.
-  std::unique_ptr<syncer::MetadataBatch> GetAllSyncMetadata() override;
-  void DeleteAllSyncMetadata() override;
-  bool UpdateEntityMetadata(syncer::ModelType model_type,
-                            const std::string& storage_key,
-                            const sync_pb::EntityMetadata& metadata) override;
-  bool ClearEntityMetadata(syncer::ModelType model_type,
-                           const std::string& storage_key) override;
-  bool UpdateModelTypeState(
-      syncer::ModelType model_type,
-      const sync_pb::ModelTypeState& model_type_state) override;
-  bool ClearModelTypeState(syncer::ModelType model_type) override;
-  void SetDeletionsHaveSyncedCallback(
-      base::RepeatingCallback<void(bool)> callback) override;
-  bool HasUnsyncedDeletions() override;
-
   // Callers that requires transaction support should call these methods to
   // begin, rollback and commit transactions. They delegate to the transaction
   // support of the underlying database. Only one transaction may exist at a
@@ -200,6 +184,9 @@
   PasswordNotesTable& password_notes_table() { return password_notes_table_; }
 
   FieldInfoTable& field_info_table() { return field_info_table_; }
+  PasswordStoreSync::MetadataStore& password_sync_metadata_store() {
+    return password_sync_metadata_store_;
+  }
 
   // Result values for encryption/decryption actions.
   enum EncryptionResult {
@@ -233,6 +220,48 @@
 
  private:
   struct PrimaryKeyAndPassword;
+  class SyncMetadataStore : public PasswordStoreSync::MetadataStore {
+   public:
+    // |db| must be not null and must outlive |this|.
+    explicit SyncMetadataStore(sql::Database* db);
+    SyncMetadataStore(const SyncMetadataStore&) = delete;
+    SyncMetadataStore& operator=(const SyncMetadataStore&) = delete;
+    ~SyncMetadataStore() override;
+
+   private:
+    // Reads all the stored sync entities metadata in a MetadataBatch. Returns
+    // nullptr in case of failure.
+    std::unique_ptr<syncer::MetadataBatch> GetAllSyncEntityMetadata();
+
+    // Reads the stored ModelTypeState. Returns nullptr in case of failure.
+    std::unique_ptr<sync_pb::ModelTypeState> GetModelTypeState();
+
+    // PasswordStoreSync::MetadataStore implementation.
+    std::unique_ptr<syncer::MetadataBatch> GetAllSyncMetadata() override;
+    void DeleteAllSyncMetadata() override;
+    bool UpdateEntityMetadata(syncer::ModelType model_type,
+                              const std::string& storage_key,
+                              const sync_pb::EntityMetadata& metadata) override;
+    bool ClearEntityMetadata(syncer::ModelType model_type,
+                             const std::string& storage_key) override;
+    bool UpdateModelTypeState(
+        syncer::ModelType model_type,
+        const sync_pb::ModelTypeState& model_type_state) override;
+
+    bool ClearModelTypeState(syncer::ModelType model_type) override;
+    void SetDeletionsHaveSyncedCallback(
+        base::RepeatingCallback<void(bool)> callback) override;
+    bool HasUnsyncedDeletions() override;
+
+    raw_ptr<sql::Database> const db_;
+    // A callback to be invoked whenever all pending deletions have been
+    // processed
+    // by Sync - see
+    // PasswordStoreSync::MetadataStore::SetDeletionsHaveSyncedCallback for more
+    // details.
+    base::RepeatingCallback<void(bool)> deletions_have_synced_callback_;
+  };
+
   FRIEND_TEST_ALL_PREFIXES(LoginDatabaseTest, AddLoginWithEncryptedPassword);
   FRIEND_TEST_ALL_PREFIXES(LoginDatabaseTest,
                            AddLoginWithEncryptedPasswordAndValue);
@@ -288,13 +317,6 @@
   PrimaryKeyAndPassword GetPrimaryKeyAndPassword(
       const PasswordForm& form) const;
 
-  // Reads all the stored sync entities metadata in a MetadataBatch. Returns
-  // nullptr in case of failure.
-  std::unique_ptr<syncer::MetadataBatch> GetAllSyncEntityMetadata();
-
-  // Reads the stored ModelTypeState. Returns nullptr in case of failure.
-  std::unique_ptr<sync_pb::ModelTypeState> GetModelTypeState();
-
   // Overwrites |key_to_form_map| with credentials retrieved from |statement|.
   // If |matched_form| is not null, filters out all results but those
   // PSL-matching |*matched_form| or federated credentials for it. If feature
@@ -344,6 +366,7 @@
   FieldInfoTable field_info_table_;
   InsecureCredentialsTable insecure_credentials_table_;
   PasswordNotesTable password_notes_table_;
+  SyncMetadataStore password_sync_metadata_store_{&db_};
 
   // These cached strings are used to build SQL statements.
   std::string add_statement_;
@@ -361,12 +384,6 @@
   std::string blocklisted_statement_;
   std::string encrypted_password_statement_by_id_;
   std::string id_and_password_statement_;
-
-  // A callback to be invoked whenever all pending deletions have been processed
-  // by Sync - see
-  // PasswordStoreSync::MetadataStore::SetDeletionsHaveSyncedCallback for more
-  // details.
-  base::RepeatingCallback<void(bool)> deletions_have_synced_callback_;
 };
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/login_database_async_helper.cc b/components/password_manager/core/browser/login_database_async_helper.cc
index 030de50..506214411 100644
--- a/components/password_manager/core/browser/login_database_async_helper.cc
+++ b/components/password_manager/core/browser/login_database_async_helper.cc
@@ -54,9 +54,10 @@
     LOG(ERROR) << "Could not create/open login database.";
   }
   if (success) {
-    login_db_->SetDeletionsHaveSyncedCallback(base::BindRepeating(
-        &LoginDatabaseAsyncHelper::NotifyDeletionsHaveSynced,
-        weak_ptr_factory_.GetWeakPtr()));
+    login_db_->password_sync_metadata_store().SetDeletionsHaveSyncedCallback(
+        base::BindRepeating(
+            &LoginDatabaseAsyncHelper::NotifyDeletionsHaveSynced,
+            weak_ptr_factory_.GetWeakPtr()));
 
     // Delay the actual reporting by 30 seconds, to ensure it doesn't happen
     // during the "hot phase" of Chrome startup.
@@ -451,7 +452,7 @@
 
 PasswordStoreSync::MetadataStore* LoginDatabaseAsyncHelper::GetMetadataStore() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return login_db_.get();
+  return login_db_ ? &login_db_->password_sync_metadata_store() : nullptr;
 }
 
 bool LoginDatabaseAsyncHelper::IsAccountStore() const {
diff --git a/components/password_manager/core/browser/login_database_unittest.cc b/components/password_manager/core/browser/login_database_unittest.cc
index 746a359..6be5c99 100644
--- a/components/password_manager/core/browser/login_database_unittest.cc
+++ b/components/password_manager/core/browser/login_database_unittest.cc
@@ -30,6 +30,7 @@
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/password_store_change.h"
+#include "components/password_manager/core/browser/password_store_sync.h"
 #include "components/password_manager/core/browser/psl_matching_helper.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
@@ -1625,7 +1626,7 @@
 
 TEST_F(LoginDatabaseTest, NoMetadata) {
   std::unique_ptr<syncer::MetadataBatch> metadata_batch =
-      db().GetAllSyncMetadata();
+      db().password_sync_metadata_store().GetAllSyncMetadata();
   ASSERT_THAT(metadata_batch, testing::NotNull());
   EXPECT_EQ(0u, metadata_batch->TakeAllMetadata().size());
   EXPECT_EQ(sync_pb::ModelTypeState().SerializeAsString(),
@@ -1634,26 +1635,29 @@
 
 TEST_F(LoginDatabaseTest, GetAllSyncMetadata) {
   sync_pb::EntityMetadata metadata;
+  PasswordStoreSync::MetadataStore& password_sync_metadata_store =
+      db().password_sync_metadata_store();
   // Storage keys must be integers.
   const std::string kStorageKey1 = "1";
   const std::string kStorageKey2 = "2";
   metadata.set_sequence_number(1);
 
-  EXPECT_TRUE(
-      db().UpdateEntityMetadata(syncer::PASSWORDS, kStorageKey1, metadata));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateEntityMetadata(
+      syncer::PASSWORDS, kStorageKey1, metadata));
 
   sync_pb::ModelTypeState model_type_state;
   model_type_state.set_initial_sync_state(
       sync_pb::ModelTypeState_InitialSyncState_INITIAL_SYNC_DONE);
 
-  EXPECT_TRUE(db().UpdateModelTypeState(syncer::PASSWORDS, model_type_state));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateModelTypeState(
+      syncer::PASSWORDS, model_type_state));
 
   metadata.set_sequence_number(2);
-  EXPECT_TRUE(
-      db().UpdateEntityMetadata(syncer::PASSWORDS, kStorageKey2, metadata));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateEntityMetadata(
+      syncer::PASSWORDS, kStorageKey2, metadata));
 
   std::unique_ptr<syncer::MetadataBatch> metadata_batch =
-      db().GetAllSyncMetadata();
+      password_sync_metadata_store.GetAllSyncMetadata();
   ASSERT_THAT(metadata_batch, testing::NotNull());
 
   EXPECT_EQ(metadata_batch->GetModelTypeState().initial_sync_state(),
@@ -1669,9 +1673,10 @@
   // Now check that a model type state update replaces the old value
   model_type_state.set_initial_sync_state(
       sync_pb::ModelTypeState_InitialSyncState_INITIAL_SYNC_STATE_UNSPECIFIED);
-  EXPECT_TRUE(db().UpdateModelTypeState(syncer::PASSWORDS, model_type_state));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateModelTypeState(
+      syncer::PASSWORDS, model_type_state));
 
-  metadata_batch = db().GetAllSyncMetadata();
+  metadata_batch = password_sync_metadata_store.GetAllSyncMetadata();
   ASSERT_THAT(metadata_batch, testing::NotNull());
   EXPECT_EQ(
       metadata_batch->GetModelTypeState().initial_sync_state(),
@@ -1680,39 +1685,44 @@
 
 TEST_F(LoginDatabaseTest, DeleteAllSyncMetadata) {
   sync_pb::EntityMetadata metadata;
+  PasswordStoreSync::MetadataStore& password_sync_metadata_store =
+      db().password_sync_metadata_store();
   // Storage keys must be integers.
   const std::string kStorageKey1 = "1";
   const std::string kStorageKey2 = "2";
   metadata.set_sequence_number(1);
 
-  EXPECT_TRUE(
-      db().UpdateEntityMetadata(syncer::PASSWORDS, kStorageKey1, metadata));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateEntityMetadata(
+      syncer::PASSWORDS, kStorageKey1, metadata));
 
   sync_pb::ModelTypeState model_type_state;
   model_type_state.set_initial_sync_state(
       sync_pb::ModelTypeState_InitialSyncState_INITIAL_SYNC_DONE);
 
-  EXPECT_TRUE(db().UpdateModelTypeState(syncer::PASSWORDS, model_type_state));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateModelTypeState(
+      syncer::PASSWORDS, model_type_state));
 
   metadata.set_sequence_number(2);
-  EXPECT_TRUE(
-      db().UpdateEntityMetadata(syncer::PASSWORDS, kStorageKey2, metadata));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateEntityMetadata(
+      syncer::PASSWORDS, kStorageKey2, metadata));
 
   std::unique_ptr<syncer::MetadataBatch> metadata_batch =
-      db().GetAllSyncMetadata();
+      password_sync_metadata_store.GetAllSyncMetadata();
   ASSERT_THAT(metadata_batch, testing::NotNull());
   ASSERT_EQ(metadata_batch->TakeAllMetadata().size(), 2u);
 
-  db().DeleteAllSyncMetadata();
+  password_sync_metadata_store.DeleteAllSyncMetadata();
 
   std::unique_ptr<syncer::MetadataBatch> empty_metadata_batch =
-      db().GetAllSyncMetadata();
+      password_sync_metadata_store.GetAllSyncMetadata();
   ASSERT_THAT(empty_metadata_batch, testing::NotNull());
   EXPECT_EQ(empty_metadata_batch->TakeAllMetadata().size(), 0u);
 }
 
 TEST_F(LoginDatabaseTest, WriteThenDeleteSyncMetadata) {
   sync_pb::EntityMetadata metadata;
+  PasswordStoreSync::MetadataStore& password_sync_metadata_store =
+      db().password_sync_metadata_store();
   const std::string kStorageKey = "1";
   sync_pb::ModelTypeState model_type_state;
 
@@ -1722,14 +1732,16 @@
   metadata.set_client_tag_hash("client_hash");
 
   // Write the data into the store.
-  EXPECT_TRUE(
-      db().UpdateEntityMetadata(syncer::PASSWORDS, kStorageKey, metadata));
-  EXPECT_TRUE(db().UpdateModelTypeState(syncer::PASSWORDS, model_type_state));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateEntityMetadata(
+      syncer::PASSWORDS, kStorageKey, metadata));
+  EXPECT_TRUE(password_sync_metadata_store.UpdateModelTypeState(
+      syncer::PASSWORDS, model_type_state));
   // Delete the data we just wrote.
-  EXPECT_TRUE(db().ClearEntityMetadata(syncer::PASSWORDS, kStorageKey));
+  EXPECT_TRUE(password_sync_metadata_store.ClearEntityMetadata(
+      syncer::PASSWORDS, kStorageKey));
 
   std::unique_ptr<syncer::MetadataBatch> metadata_batch =
-      db().GetAllSyncMetadata();
+      password_sync_metadata_store.GetAllSyncMetadata();
   ASSERT_THAT(metadata_batch, testing::NotNull());
 
   // It shouldn't be there any more.
@@ -1738,8 +1750,9 @@
   EXPECT_EQ(metadata_records.size(), 0u);
 
   // Now delete the model type state.
-  EXPECT_TRUE(db().ClearModelTypeState(syncer::PASSWORDS));
-  metadata_batch = db().GetAllSyncMetadata();
+  EXPECT_TRUE(
+      password_sync_metadata_store.ClearModelTypeState(syncer::PASSWORDS));
+  metadata_batch = password_sync_metadata_store.GetAllSyncMetadata();
   ASSERT_THAT(metadata_batch, testing::NotNull());
 
   EXPECT_EQ(sync_pb::ModelTypeState().SerializeAsString(),
diff --git a/components/password_manager/core/browser/password_list_sorter.cc b/components/password_manager/core/browser/password_list_sorter.cc
index 7697257..7defb38 100644
--- a/components/password_manager/core/browser/password_list_sorter.cc
+++ b/components/password_manager/core/browser/password_list_sorter.cc
@@ -25,6 +25,10 @@
 // this character should be alphabetically smaller than real federations.
 constexpr char kSortKeyNoFederationSymbol = '-';
 
+// Symbols to differentiate between passwords and passkeys.
+constexpr char kSortKeyPasskeySymbol = 'k';
+constexpr char kSortKeyPasswordSymbol = 'w';
+
 }  // namespace
 
 std::string CreateSortKey(const PasswordForm& form, IgnoreStore ignore_store) {
@@ -85,6 +89,10 @@
   // To separate HTTP/HTTPS credentials, add the scheme to the key.
   key += kSortKeyPartsSeparator + GetShownUrl(credential).scheme();
 
+  // Separate passwords from passkeys.
+  key += kSortKeyPartsSeparator;
+  key += credential.is_passkey ? kSortKeyPasskeySymbol : kSortKeyPasswordSymbol;
+
   return key;
 }
 
diff --git a/components/password_manager/core/browser/password_list_sorter.h b/components/password_manager/core/browser/password_list_sorter.h
index 7cfba63..d48e2217 100644
--- a/components/password_manager/core/browser/password_list_sorter.h
+++ b/components/password_manager/core/browser/password_list_sorter.h
@@ -30,7 +30,8 @@
 // If |ignore_store| is true, forms differing only by the originating password
 // store will map to the same key.
 std::string CreateSortKey(const PasswordForm& form, IgnoreStore ignore_store);
-// Same as |CreateSortKey| for |PasswordForm| but it always ignores store.
+// Same as |CreateSortKey| for |PasswordForm| but it always ignores store and
+// takes passkeys into account.
 // TODO(vsemeniuk): find a better name for this function.
 std::string CreateSortKey(const CredentialUIEntry& credential);
 
diff --git a/components/password_manager/core/browser/password_list_sorter_unittest.cc b/components/password_manager/core/browser/password_list_sorter_unittest.cc
index 3e271cd1..0b55d8e 100644
--- a/components/password_manager/core/browser/password_list_sorter_unittest.cc
+++ b/components/password_manager/core/browser/password_list_sorter_unittest.cc
@@ -7,7 +7,9 @@
 #include <vector>
 
 #include "base/strings/utf_string_conversions.h"
+#include "components/password_manager/core/browser/passkey_credential.h"
 #include "components/password_manager/core/browser/password_form.h"
+#include "components/password_manager/core/browser/ui/credential_ui_entry.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -236,4 +238,18 @@
   EXPECT_EQ(CreateUsernamePasswordSortKey(form), "g.com");
 }
 
+TEST(PasswordListSorterTest, PasskeyVsPasswordSortKey) {
+  PasswordForm form;
+  form.signon_realm = "https://test.com/";
+  form.url = GURL(form.signon_realm);
+  form.username_value = u"lora";
+  CredentialUIEntry password(std::move(form));
+
+  PasskeyCredential passkey_credential(PasskeyCredential::Source::kAndroidPhone,
+                                       "test.com", {}, {}, "lora");
+  CredentialUIEntry passkey(std::move(passkey_credential));
+
+  EXPECT_NE(CreateSortKey(password), CreateSortKey(passkey));
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index 0fa55b3..26324e6 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -549,8 +549,6 @@
 
 void PasswordManager::OnPasswordFormSubmitted(PasswordManagerDriver* driver,
                                               const FormData& form_data) {
-  base::UmaHistogramEnumeration("PasswordManager.FormSubmission.PerProfileType",
-                                client_->GetProfileType());
   ProvisionallySaveForm(form_data, driver, false);
 }
 
diff --git a/components/password_manager/core/browser/password_manager_driver.h b/components/password_manager/core/browser/password_manager_driver.h
index 79b2e2d..a3e65ae 100644
--- a/components/password_manager/core/browser/password_manager_driver.h
+++ b/components/password_manager/core/browser/password_manager_driver.h
@@ -92,9 +92,11 @@
       const std::u16string& user_provided_credential) {}
 
 #if BUILDFLAG(IS_ANDROID)
-  // Informs the renderer that the Touch To Fill sheet has been closed.
-  // Indicates whether the virtual keyboard should be shown instead.
-  virtual void TouchToFillClosed(ShowVirtualKeyboard show_virtual_keyboard) {}
+  // Informs the renderer that the keyboard replacing surface (e.g. Touch To
+  // Fill sheet) has been closed. Indicates whether the virtual keyboard should
+  // be shown instead.
+  virtual void KeyboardReplacingSurfaceClosed(
+      ShowVirtualKeyboard show_virtual_keyboard) {}
 
   // Triggers form submission on the last interacted web input element.
   virtual void TriggerFormSubmission() {}
diff --git a/components/password_manager/core/browser/password_manager_unittest.cc b/components/password_manager/core/browser/password_manager_unittest.cc
index 9de6858..cbbca60 100644
--- a/components/password_manager/core/browser/password_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_unittest.cc
@@ -4254,8 +4254,8 @@
   EXPECT_TRUE(manager()->IsFormManagerPendingPasswordUpdate());
 }
 
-// Test submission of "PasswordManager.FormVisited.PerProfileType" and
-// "PasswordManager.FormSubmission.PerProfileType" for Incognito mode.
+// Test submission of "PasswordManager.FormVisited.PerProfileType" for
+// Incognito mode.
 TEST_F(PasswordManagerTest, IncognitoProfileTypeMetricSubmission) {
   base::HistogramTester histogram_tester;
 
@@ -4272,27 +4272,18 @@
   histogram_tester.ExpectUniqueSample(
       "PasswordManager.FormVisited.PerProfileType",
       profile_metrics::BrowserProfileType::kIncognito, 1);
-  histogram_tester.ExpectTotalCount(
-      "PasswordManager.FormSubmission.PerProfileType", 0);
 
   EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url))
       .WillRepeatedly(Return(true));
   OnPasswordFormSubmitted(form.form_data);
 
-  // Test if submission is properly recorded.
-  histogram_tester.ExpectUniqueSample(
-      "PasswordManager.FormSubmission.PerProfileType",
-      profile_metrics::BrowserProfileType::kIncognito, 1);
-
   // And nothing in other buckets.
   histogram_tester.ExpectTotalCount(
       "PasswordManager.FormVisited.PerProfileType", 1);
-  histogram_tester.ExpectTotalCount(
-      "PasswordManager.FormSubmission.PerProfileType", 1);
 }
 
-// Test submission of "PasswordManager.FormVisited.PerProfileType" and
-// "PasswordManager.FormSubmission.PerProfileType" for Guest mode.
+// Test submission of "PasswordManager.FormVisited.PerProfileType" for Guest
+// mode.
 TEST_F(PasswordManagerTest, GuestProfileTypeMetricSubmission) {
   base::HistogramTester histogram_tester;
 
@@ -4309,23 +4300,14 @@
   histogram_tester.ExpectUniqueSample(
       "PasswordManager.FormVisited.PerProfileType",
       profile_metrics::BrowserProfileType::kGuest, 1);
-  histogram_tester.ExpectTotalCount(
-      "PasswordManager.FormSubmission.PerProfileType", 0);
 
   EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url))
       .WillRepeatedly(Return(true));
   OnPasswordFormSubmitted(form.form_data);
 
-  // Test if submission is properly recorded.
-  histogram_tester.ExpectUniqueSample(
-      "PasswordManager.FormSubmission.PerProfileType",
-      profile_metrics::BrowserProfileType::kGuest, 1);
-
   // And nothing in other buckets.
   histogram_tester.ExpectTotalCount(
       "PasswordManager.FormVisited.PerProfileType", 1);
-  histogram_tester.ExpectTotalCount(
-      "PasswordManager.FormSubmission.PerProfileType", 1);
 }
 
 // Tests that the login is not detected twice.
diff --git a/components/password_manager/core/browser/password_store_proxy_backend.cc b/components/password_manager/core/browser/password_store_proxy_backend.cc
index aeae061..4cc95e2 100644
--- a/components/password_manager/core/browser/password_store_proxy_backend.cc
+++ b/components/password_manager/core/browser/password_store_proxy_backend.cc
@@ -15,6 +15,7 @@
 #include "base/functional/identity.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "components/password_manager/core/browser/field_info_table.h"
@@ -36,65 +37,17 @@
 
 bool ShouldExecuteModifyOperationsOnShadowBackend(PrefService* prefs,
                                                   bool is_syncing) {
-  // TODO(crbug.com/1306001): Reenable or clean up for local-only users.
   return false;
 }
 
 bool ShouldExecuteReadOperationsOnShadowBackend(PrefService* prefs,
                                                 bool is_syncing) {
-  if (ShouldExecuteModifyOperationsOnShadowBackend(prefs, is_syncing)) {
-    // Read operations are always allowed whenever modifications are allowed.
-    // i.e. necessary migrations have happened and appropriate flags are set.
-    return true;
-  }
-
-  if (!is_syncing)
-    return false;
-
-  if (!base::FeatureList::IsEnabled(features::kUnifiedPasswordManagerAndroid))
-    return false;
-
-  features::UpmExperimentVariation variation =
-      features::kUpmExperimentVariationParam.Get();
-  switch (variation) {
-    case features::UpmExperimentVariation::kEnableForSyncingUsers:
-    case features::UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers:
-    case features::UpmExperimentVariation::kEnableForAllUsers:
-      return false;
-    case features::UpmExperimentVariation::kShadowSyncingUsers:
-      return true;
-  }
-  NOTREACHED() << "Define explicitly whether shadow traffic is recorded!";
   return false;
 }
 
 bool ShouldExecuteDeletionsOnShadowBackend(PrefService* prefs,
                                            bool is_syncing) {
-  if (ShouldExecuteModifyOperationsOnShadowBackend(prefs, is_syncing))
-    return true;
-
-  if (!is_syncing)
-    return false;
-
-  if (!base::FeatureList::IsEnabled(features::kUnifiedPasswordManagerAndroid))
-    return false;
-
-  features::UpmExperimentVariation variation =
-      features::kUpmExperimentVariationParam.Get();
-  switch (variation) {
-    case features::UpmExperimentVariation::kEnableForSyncingUsers:
-    case features::UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers:
-      return true;
-    case features::UpmExperimentVariation::kEnableForAllUsers:
-      // All passwords are in the remote storage. There should not be a
-      // shadow backend anymore.
-      return false;
-    case features::UpmExperimentVariation::kShadowSyncingUsers:
-      return false;
-  }
-  NOTREACHED()
-      << "Define explicitly whether deletions on both backends are required!";
-  return false;
+  return is_syncing;
 }
 
 bool ShouldErrorResultInFallback(PasswordStoreBackendError error) {
@@ -109,21 +62,7 @@
 }
 
 bool IsBuiltInBackendSyncEnabled() {
-  DCHECK(
-      base::FeatureList::IsEnabled(features::kUnifiedPasswordManagerAndroid));
-
-  features::UpmExperimentVariation variation =
-      features::kUpmExperimentVariationParam.Get();
-  switch (variation) {
-    case features::UpmExperimentVariation::kEnableForSyncingUsers:
-    case features::UpmExperimentVariation::kShadowSyncingUsers:
-    case features::UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers:
-      return true;
-    case features::UpmExperimentVariation::kEnableForAllUsers:
-      return false;
-  }
-  NOTREACHED() << "Define which backend handles sync change callbacks!";
-  return false;
+  return true;
 }
 
 void CallOnSyncEnabledOrDisabledForEnabledBackend(
@@ -686,22 +625,14 @@
 
 std::unique_ptr<syncer::ProxyModelTypeControllerDelegate>
 PasswordStoreProxyBackend::CreateSyncControllerDelegate() {
-  switch (features::kUpmExperimentVariationParam.Get()) {
-    case features::UpmExperimentVariation::kEnableForSyncingUsers:
-    case features::UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers:
-    case features::UpmExperimentVariation::kShadowSyncingUsers:
-      DCHECK(!base::FeatureList::IsEnabled(
-          features::kUnifiedPasswordManagerSyncUsingAndroidBackendOnly))
-          << "Without support for local passwords, use legacy sync controller";
-      return built_in_backend_->CreateSyncControllerDelegate();
-    case features::UpmExperimentVariation::kEnableForAllUsers:
-      return base::FeatureList::IsEnabled(
-                 features::kUnifiedPasswordManagerSyncUsingAndroidBackendOnly)
-                 ? android_backend_->CreateSyncControllerDelegate()
-                 : built_in_backend_->CreateSyncControllerDelegate();
+  if (base::FeatureList::IsEnabled(
+          features::kUnifiedPasswordManagerSyncUsingAndroidBackendOnly)) {
+    // The android backend (PasswordStoreAndroidBackend) creates a controller
+    // delegate that prevents sync from actually communicating with the sync
+    // server using the built in SyncEngine.
+    return android_backend_->CreateSyncControllerDelegate();
   }
-  NOTREACHED() << "Define which backend creates the sync delegate.";
-  return nullptr;
+  return built_in_backend_->CreateSyncControllerDelegate();
 }
 
 void PasswordStoreProxyBackend::ClearAllLocalPasswords() {
@@ -763,21 +694,7 @@
   if (!IsPasswordSyncEnabled(sync_service_))
     return false;
 
-  if (!base::FeatureList::IsEnabled(features::kUnifiedPasswordManagerAndroid))
-    return false;
-
-  features::UpmExperimentVariation variation =
-      features::kUpmExperimentVariationParam.Get();
-  switch (variation) {
-    case features::UpmExperimentVariation::kEnableForSyncingUsers:
-    case features::UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers:
-    case features::UpmExperimentVariation::kEnableForAllUsers:
-      return true;
-    case features::UpmExperimentVariation::kShadowSyncingUsers:
-      return false;
-  }
-  NOTREACHED() << "Define explicitly whether Android is the main backend!";
-  return false;
+  return true;
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store_proxy_backend_unittest.cc b/components/password_manager/core/browser/password_store_proxy_backend_unittest.cc
index 5ba9f41..c6f4343 100644
--- a/components/password_manager/core/browser/password_store_proxy_backend_unittest.cc
+++ b/components/password_manager/core/browser/password_store_proxy_backend_unittest.cc
@@ -114,9 +114,6 @@
     proxy_backend_ = std::make_unique<PasswordStoreProxyBackend>(
         &built_in_backend_, &android_backend_, &prefs_);
 
-    feature_list_.InitAndEnableFeatureWithParameters(
-        features::kUnifiedPasswordManagerAndroid, {{"stage", "1"}});
-
     prefs_.registry()->RegisterIntegerPref(
         prefs::kCurrentMigrationVersionToGoogleMobileServices, 0);
     prefs_.registry()->RegisterBooleanPref(
@@ -200,10 +197,6 @@
 }
 
 TEST_F(PasswordStoreProxyBackendTest, CallRemoteChangesOnlyForMainBackend) {
-  // Use the rollout stage which changes the main backend with the sync status.
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid, {{"stage", "0"}});
   base::MockCallback<RemoveChangesReceived> original_callback;
 
   // Both backends receive a callback that they trigger for new remote changes.
@@ -239,10 +232,6 @@
 }
 
 TEST_F(PasswordStoreProxyBackendTest, CallSyncCallbackOnlyForBuiltInBackend) {
-  // Use the rollout stage which changes the main backend with the sync status.
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid, {{"stage", "0"}});
   base::MockCallback<base::RepeatingClosure> original_callback;
 
   // Both backends receive a callback that they trigger for new remote changes.
@@ -283,12 +272,12 @@
   std::vector<std::unique_ptr<PasswordForm>> expected_logins =
       CreateTestLogins();
   EXPECT_CALL(mock_reply, Run(LoginsResultsOrErrorAre(&expected_logins)));
-  EXPECT_CALL(built_in_backend(), GetAllLoginsAsync)
+  EXPECT_CALL(android_backend(), GetAllLoginsAsync)
       .WillOnce(WithArg<0>(Invoke([](LoginsOrErrorReply reply) -> void {
         std::move(reply).Run(CreateTestLogins());
       })));
   EnablePasswordSync();
-  EXPECT_CALL(android_backend(), GetAllLoginsAsync);
+  EXPECT_CALL(built_in_backend(), GetAllLoginsAsync).Times(0);
   proxy_backend().GetAllLoginsAsync(mock_reply.Get());
 }
 
@@ -298,12 +287,12 @@
   std::vector<std::unique_ptr<PasswordForm>> expected_logins =
       CreateTestLogins();
   EXPECT_CALL(mock_reply, Run(LoginsResultsOrErrorAre(&expected_logins)));
-  EXPECT_CALL(built_in_backend(), GetAutofillableLoginsAsync)
+  EXPECT_CALL(android_backend(), GetAutofillableLoginsAsync)
       .WillOnce(WithArg<0>(Invoke([](LoginsOrErrorReply reply) -> void {
         std::move(reply).Run(CreateTestLogins());
       })));
+  EXPECT_CALL(built_in_backend(), GetAutofillableLoginsAsync).Times(0);
   EnablePasswordSync();
-  EXPECT_CALL(android_backend(), GetAutofillableLoginsAsync);
   proxy_backend().GetAutofillableLoginsAsync(mock_reply.Get());
 }
 
@@ -312,12 +301,12 @@
   std::vector<std::unique_ptr<PasswordForm>> expected_logins =
       CreateTestLogins();
   EXPECT_CALL(mock_reply, Run(LoginsResultsOrErrorAre(&expected_logins)));
-  EXPECT_CALL(built_in_backend(), FillMatchingLoginsAsync)
+  EXPECT_CALL(android_backend(), FillMatchingLoginsAsync)
       .WillOnce(WithArg<0>(Invoke([](LoginsOrErrorReply reply) -> void {
         std::move(reply).Run(CreateTestLogins());
       })));
   EnablePasswordSync();
-  EXPECT_CALL(android_backend(), FillMatchingLoginsAsync);
+  EXPECT_CALL(built_in_backend(), FillMatchingLoginsAsync).Times(0);
   proxy_backend().FillMatchingLoginsAsync(mock_reply.Get(),
                                           /*include_psl=*/false,
                                           std::vector<PasswordFormDigest>());
@@ -330,9 +319,8 @@
   change_list.push_back(PasswordStoreChange(Type::ADD, form));
   EXPECT_CALL(mock_reply,
               Run(VariantWith<PasswordChanges>(Optional(change_list))));
-  // This test doesn't care about the shadow backend.
-  EXPECT_CALL(android_backend(), AddLoginAsync).Times(AnyNumber());
-  EXPECT_CALL(built_in_backend(), AddLoginAsync(Eq(form), _))
+  EXPECT_CALL(built_in_backend(), AddLoginAsync).Times(0);
+  EXPECT_CALL(android_backend(), AddLoginAsync(Eq(form), _))
       .WillOnce(WithArg<1>(
           Invoke([&change_list](PasswordChangesOrErrorReply reply) -> void {
             std::move(reply).Run(change_list);
@@ -348,8 +336,8 @@
   EXPECT_CALL(mock_reply,
               Run(VariantWith<PasswordChanges>(Optional(change_list))));
   // This test doesn't care about the shadow backend.
-  EXPECT_CALL(android_backend(), UpdateLoginAsync).Times(AnyNumber());
-  EXPECT_CALL(built_in_backend(), UpdateLoginAsync(Eq(form), _))
+  EXPECT_CALL(built_in_backend(), UpdateLoginAsync).Times(0);
+  EXPECT_CALL(android_backend(), UpdateLoginAsync(Eq(form), _))
       .WillOnce(WithArg<1>(
           Invoke([&change_list](PasswordChangesOrErrorReply reply) -> void {
             std::move(reply).Run(change_list);
@@ -364,9 +352,9 @@
   change_list.push_back(PasswordStoreChange(Type::REMOVE, form));
   EXPECT_CALL(mock_reply,
               Run(VariantWith<PasswordChanges>(Optional(change_list))));
-  // This test doesn't care about the shadow backend.
-  EXPECT_CALL(android_backend(), RemoveLoginAsync).Times(AnyNumber());
-  EXPECT_CALL(built_in_backend(), RemoveLoginAsync(Eq(form), _))
+  // Removals are always mirrored on the built-in backend.
+  EXPECT_CALL(built_in_backend(), RemoveLoginAsync(Eq(form), _));
+  EXPECT_CALL(android_backend(), RemoveLoginAsync(Eq(form), _))
       .WillOnce(WithArg<1>(
           Invoke([&change_list](PasswordChangesOrErrorReply reply) -> void {
             std::move(reply).Run(change_list);
@@ -383,10 +371,10 @@
   change_list.push_back(PasswordStoreChange(Type::REMOVE, CreateTestForm()));
   EXPECT_CALL(mock_reply,
               Run(VariantWith<PasswordChanges>(Optional(change_list))));
-  // This test doesn't care about the shadow backend.
-  EXPECT_CALL(android_backend(), RemoveLoginsByURLAndTimeAsync)
-      .Times(AnyNumber());
+  // Removals are always mirrored on the built-in backend.
   EXPECT_CALL(built_in_backend(),
+              RemoveLoginsByURLAndTimeAsync(_, Eq(kStart), Eq(kEnd), _, _));
+  EXPECT_CALL(android_backend(),
               RemoveLoginsByURLAndTimeAsync(_, Eq(kStart), Eq(kEnd), _, _))
       .WillOnce(WithArg<4>(
           Invoke([&change_list](PasswordChangesOrErrorReply reply) -> void {
@@ -406,10 +394,10 @@
   change_list.push_back(PasswordStoreChange(Type::REMOVE, CreateTestForm()));
   EXPECT_CALL(mock_reply,
               Run(VariantWith<PasswordChanges>(Optional(change_list))));
-  // This test doesn't care about the shadow backend.
-  EXPECT_CALL(android_backend(), RemoveLoginsCreatedBetweenAsync)
-      .Times(AnyNumber());
+  // Removals are always mirrored on the built-in backend.
   EXPECT_CALL(built_in_backend(),
+              RemoveLoginsCreatedBetweenAsync(Eq(kStart), Eq(kEnd), _));
+  EXPECT_CALL(android_backend(),
               RemoveLoginsCreatedBetweenAsync(Eq(kStart), Eq(kEnd), _))
       .WillOnce(WithArg<2>(
           Invoke([&change_list](PasswordChangesOrErrorReply reply) -> void {
@@ -423,10 +411,8 @@
        UseMainBackendToDisableAutoSignInForOriginsAsync) {
   base::MockCallback<base::OnceClosure> mock_reply;
   EXPECT_CALL(mock_reply, Run);
-  // This test doesn't care about the shadow backend.
+  EXPECT_CALL(built_in_backend(), DisableAutoSignInForOriginsAsync).Times(0);
   EXPECT_CALL(android_backend(), DisableAutoSignInForOriginsAsync)
-      .Times(AnyNumber());
-  EXPECT_CALL(built_in_backend(), DisableAutoSignInForOriginsAsync)
       .WillOnce(WithArg<1>(
           Invoke([](base::OnceClosure reply) { std::move(reply).Run(); })));
   proxy_backend().DisableAutoSignInForOriginsAsync(
@@ -435,12 +421,12 @@
 
 TEST_F(PasswordStoreProxyBackendTest,
        UseMainBackendToGetSmartBubbleStatsStore) {
-  EXPECT_CALL(built_in_backend(), GetSmartBubbleStatsStore);
+  EXPECT_CALL(android_backend(), GetSmartBubbleStatsStore);
   proxy_backend().GetSmartBubbleStatsStore();
 }
 
 TEST_F(PasswordStoreProxyBackendTest, UseMainBackendToGetFieldInfoStore) {
-  EXPECT_CALL(built_in_backend(), GetFieldInfoStore);
+  EXPECT_CALL(android_backend(), GetFieldInfoStore);
   proxy_backend().GetFieldInfoStore();
 }
 
@@ -448,8 +434,7 @@
        NoShadowGetAllLoginsAsyncWithoutSyncOrMigration) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   base::HistogramTester histogram_tester;
@@ -482,8 +467,7 @@
        NoShadowGetAutofillableLoginsAsyncWithoutSyncOrMigration) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
   DisablePasswordSync();
 
@@ -496,8 +480,7 @@
        NoShadowFillMatchingLoginsAsyncWithoutSyncOrMigration) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
   DisablePasswordSync();
 
@@ -511,8 +494,8 @@
 TEST_F(PasswordStoreProxyBackendTest, NoShadowAddLoginAsyncWhenSyncEnabled) {
   EnablePasswordSync();
 
-  EXPECT_CALL(built_in_backend(), AddLoginAsync);
-  EXPECT_CALL(android_backend(), AddLoginAsync).Times(0);
+  EXPECT_CALL(built_in_backend(), AddLoginAsync).Times(0);
+  EXPECT_CALL(android_backend(), AddLoginAsync);
   proxy_backend().AddLoginAsync(CreateTestForm(),
                                 /*callback=*/base::DoNothing());
 }
@@ -521,8 +504,7 @@
        NoShadowAddLoginAsyncWhenSyncDisabledAndInitialMigrationIncomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   DisablePasswordSync();
@@ -536,8 +518,8 @@
 TEST_F(PasswordStoreProxyBackendTest, NoShadowUpdateLoginAsyncWhenSyncEnabled) {
   EnablePasswordSync();
 
-  EXPECT_CALL(built_in_backend(), UpdateLoginAsync);
-  EXPECT_CALL(android_backend(), UpdateLoginAsync).Times(0);
+  EXPECT_CALL(built_in_backend(), UpdateLoginAsync).Times(0);
+  EXPECT_CALL(android_backend(), UpdateLoginAsync);
   proxy_backend().UpdateLoginAsync(CreateTestForm(),
                                    /*callback=*/base::DoNothing());
 }
@@ -546,8 +528,7 @@
        NoShadowUpdateLoginAsyncWhenSyncDisabledAndInitialMigrationIncomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   DisablePasswordSync();
@@ -558,21 +539,11 @@
                                    /*callback=*/base::DoNothing());
 }
 
-TEST_F(PasswordStoreProxyBackendTest, NoShadowRemoveLoginAsyncWhenSyncEnabled) {
-  EnablePasswordSync();
-
-  EXPECT_CALL(built_in_backend(), RemoveLoginAsync);
-  EXPECT_CALL(android_backend(), RemoveLoginAsync).Times(0);
-  proxy_backend().RemoveLoginAsync(CreateTestForm(),
-                                   /*callback=*/base::DoNothing());
-}
-
 TEST_F(PasswordStoreProxyBackendTest,
        NoShadowRemoveLoginAsyncWhenSyncDisabledAndInitialMigrationIncomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   DisablePasswordSync();
@@ -587,8 +558,7 @@
        ShadowRemoveLoginAsyncWhenSyncEnabledAtEnabledForSyncingUsersStage) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "1"}, {"stage", "0"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "1"}});
 
   EnablePasswordSync();
 
@@ -598,27 +568,12 @@
                                    /*callback=*/base::DoNothing());
 }
 
-TEST_F(PasswordStoreProxyBackendTest,
-       NoShadowRemoveLoginsByURLAndTimeAsyncWhenSyncEnabled) {
-  EnablePasswordSync();
-
-  EXPECT_CALL(built_in_backend(), RemoveLoginsByURLAndTimeAsync);
-  EXPECT_CALL(android_backend(), RemoveLoginsByURLAndTimeAsync).Times(0);
-  proxy_backend().RemoveLoginsByURLAndTimeAsync(
-      base::BindRepeating(&FilterNoUrl),
-      /*delete_begin=*/base::Time::FromTimeT(111111),
-      /*delete_end=*/base::Time::FromTimeT(22222222),
-      /*sync_completion=*/base::OnceCallback<void(bool)>(),
-      /*callback=*/base::DoNothing());
-}
-
 TEST_F(
     PasswordStoreProxyBackendTest,
     NoShadowRemoveLoginsByURLAndTimeAsyncWhenSyncDisabledAndInitialMigrationIncomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   DisablePasswordSync();
@@ -638,8 +593,7 @@
     ShadowRemoveLoginsByURLAndTimeAsyncWhenSyncEnabledAtEnabledForSyncingUsersStage) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "1"}, {"stage", "0"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "1"}});
 
   EnablePasswordSync();
 
@@ -653,25 +607,12 @@
       /*callback=*/base::DoNothing());
 }
 
-TEST_F(PasswordStoreProxyBackendTest,
-       NoShadowRemoveLoginsCreatedBetweenAsyncWhenSyncEnabled) {
-  EnablePasswordSync();
-
-  EXPECT_CALL(built_in_backend(), RemoveLoginsCreatedBetweenAsync);
-  EXPECT_CALL(android_backend(), RemoveLoginsCreatedBetweenAsync).Times(0);
-  proxy_backend().RemoveLoginsCreatedBetweenAsync(
-      /*delete_begin=*/base::Time::FromTimeT(111111),
-      /*delete_end=*/base::Time::FromTimeT(22222222),
-      /*callback=*/base::DoNothing());
-}
-
 TEST_F(
     PasswordStoreProxyBackendTest,
     NoShadowRemoveLoginsCreatedBetweenAsyncWhenSyncDisabledAndInitialMigrationIncomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   DisablePasswordSync();
@@ -689,8 +630,7 @@
     ShadowRemoveLoginsCreatedBetweenAsyncWhenSyncEnabledAtEnabledForSyncingUsersStage) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "1"}, {"stage", "0"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "1"}});
 
   EnablePasswordSync();
 
@@ -702,23 +642,12 @@
       /*callback=*/base::DoNothing());
 }
 
-TEST_F(PasswordStoreProxyBackendTest,
-       NoShadowDisableAutoSignInForOriginsAsyncWhenSyncEnabled) {
-  EnablePasswordSync();
-
-  EXPECT_CALL(built_in_backend(), DisableAutoSignInForOriginsAsync);
-  EXPECT_CALL(android_backend(), DisableAutoSignInForOriginsAsync).Times(0);
-  proxy_backend().DisableAutoSignInForOriginsAsync(
-      base::BindRepeating(&FilterNoUrl), /*completion=*/base::DoNothing());
-}
-
 TEST_F(
     PasswordStoreProxyBackendTest,
     NoShadowDisableAutoSignInForOriginsAsyncWhenSyncDisabledAndInitialMigrationIncomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid,
-      {{"migration_version", "2"}, {"stage", "1"}});
+      features::kUnifiedPasswordManagerAndroid, {{"migration_version", "2"}});
   prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 1);
 
   DisablePasswordSync();
@@ -737,12 +666,7 @@
 }
 
 TEST_F(PasswordStoreProxyBackendTest,
-       UsesAndroidBackendAsMainBackendPasswordSyncDisabledInSettings) {
-  base::test::ScopedFeatureList feature_list;
-  // Enable UPM for syncing users only.
-  feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid, {{"stage", "2"}});
-
+       UsesBuiltInBackendAsMainBackendPasswordSyncDisabledInSettings) {
   // Imitate password sync being disabled in settings.
   DisablePasswordSync();
 
@@ -755,9 +679,6 @@
 TEST_F(PasswordStoreProxyBackendTest,
        UsesBuiltInBackendAsMainBackendToGetLoginsUserUnenrolledFromUPM) {
   base::test::ScopedFeatureList feature_list;
-  // Enable UPM for syncing users only.
-  feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid, {{"stage", "2"}});
   prefs()->SetBoolean(prefs::kUnenrolledFromGoogleMobileServicesDueToErrors,
                       true);
 
@@ -774,9 +695,6 @@
 TEST_F(PasswordStoreProxyBackendTest,
        UsesBuiltInBackendAsMainBackendToAddLoginUserUnenrolledFromUPM) {
   base::test::ScopedFeatureList feature_list;
-  // Enable UPM for syncing users only.
-  feature_list.InitAndEnableFeatureWithParameters(
-      features::kUnifiedPasswordManagerAndroid, {{"stage", "2"}});
   prefs()->SetBoolean(prefs::kUnenrolledFromGoogleMobileServicesDueToErrors,
                       true);
 
@@ -803,11 +721,6 @@
        RetriesAddLoginOnBuiltInBackend) {
   const FallbackParam& p = GetParam();
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      password_manager::features::kUnifiedPasswordManagerAndroid,
-      {{"stage", "0"}});
-
   base::HistogramTester histogram_tester;
 
   EnablePasswordSync();
@@ -843,11 +756,6 @@
        RetriesUpdateLoginOnBuiltInBackend) {
   const FallbackParam& p = GetParam();
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      password_manager::features::kUnifiedPasswordManagerAndroid,
-      {{"stage", "0"}});
-
   base::HistogramTester histogram_tester;
 
   EnablePasswordSync();
@@ -883,10 +791,6 @@
        RetriesFillMatchingLoginsOnBuiltInBackend) {
   const FallbackParam& p = GetParam();
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      password_manager::features::kUnifiedPasswordManagerAndroid,
-      {{"stage", "0"}});
   base::HistogramTester histogram_tester;
 
   EnablePasswordSync();
@@ -969,125 +873,8 @@
   size_t inconsistent;  // Number of common logins that diffen in the passwords.
 };
 
-class PasswordStoreProxyBackendTestWithLoginsParams
-    : public PasswordStoreProxyBackendTest,
-      public testing::WithParamInterface<LoginsMetricsParam> {};
-
-// Tests the metrics of GetAllLoginsAsync().
-TEST_P(PasswordStoreProxyBackendTestWithLoginsParams,
-       GetAllLoginsAsyncMetrics) {
-  const LoginsMetricsParam& p = GetParam();
-  base::HistogramTester histogram_tester;
-  base::MockCallback<LoginsOrErrorReply> mock_reply;
-  {
-    EXPECT_CALL(mock_reply, Run(_));
-    EXPECT_CALL(built_in_backend(), GetAllLoginsAsync)
-        .WillOnce(WithArg<0>(Invoke([&p](LoginsOrErrorReply reply) -> void {
-          std::move(reply).Run(p.GetMainLogins());
-        })));
-    EnablePasswordSync();
-    EXPECT_CALL(android_backend(), GetAllLoginsAsync)
-        .WillOnce(WithArg<0>(Invoke([&p](LoginsOrErrorReply reply) -> void {
-          std::move(reply).Run(p.GetShadowLogins());
-        })));
-    proxy_backend().GetAllLoginsAsync(mock_reply.Get());
-  }
-
-  std::string prefix =
-      "PasswordManager.PasswordStoreProxyBackend.GetAllLoginsAsync.";
-
-  histogram_tester.ExpectUniqueSample(prefix + "Diff.Abs",
-                                      p.in_main + p.in_shadow, 1);
-  histogram_tester.ExpectUniqueSample(prefix + "MainMinusShadow.Abs", p.in_main,
-                                      1);
-  histogram_tester.ExpectUniqueSample(prefix + "ShadowMinusMain.Abs",
-                                      p.in_shadow, 1);
-  histogram_tester.ExpectUniqueSample(prefix + "InconsistentPasswords.Abs",
-                                      p.inconsistent, 1);
-
-  // Returns ⌈nominator/denominator⌉.
-  auto percentage = [](size_t nominator, size_t denominator) {
-    return static_cast<size_t>(std::ceil(static_cast<double>(nominator) /
-                                         static_cast<double>(denominator) *
-                                         100.0l));
-  };
-
-  size_t total = p.in_common + p.in_main + p.in_shadow;
-  if (total != 0) {
-    histogram_tester.ExpectUniqueSample(
-        prefix + "Diff.Rel", percentage(p.in_main + p.in_shadow, total), 1);
-    histogram_tester.ExpectUniqueSample(prefix + "MainMinusShadow.Rel",
-                                        percentage(p.in_main, total), 1);
-    histogram_tester.ExpectUniqueSample(prefix + "ShadowMinusMain.Rel",
-                                        percentage(p.in_shadow, total), 1);
-  } else {
-    histogram_tester.ExpectTotalCount(prefix + "Diff.Rel", 0);
-    histogram_tester.ExpectTotalCount(prefix + "MainMinusShadow.Rel", 0);
-    histogram_tester.ExpectTotalCount(prefix + "ShadowMinusMain.Rel", 0);
-  }
-
-  if (p.in_common != 0) {
-    histogram_tester.ExpectUniqueSample(prefix + "InconsistentPasswords.Rel",
-                                        percentage(p.inconsistent, p.in_common),
-                                        1);
-  } else {
-    histogram_tester.ExpectTotalCount(prefix + "InconsistentPasswords.Rel", 0);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    PasswordStoreProxyBackendTest,
-    PasswordStoreProxyBackendTestWithLoginsParams,
-    testing::Values(LoginsMetricsParam{.main_logins = {},
-                                       .shadow_logins = {},
-                                       .in_common = 0,
-                                       .in_main = 0,
-                                       .in_shadow = 0,
-                                       .inconsistent = 0},
-                    LoginsMetricsParam{
-                        .main_logins = {{"user1", "pswd1", "https://a.com/"}},
-                        .shadow_logins = {{"user1", "pswd1", "https://a.com/"}},
-                        .in_common = 1,
-                        .in_main = 0,
-                        .in_shadow = 0,
-                        .inconsistent = 0},
-                    LoginsMetricsParam{
-                        .main_logins = {{"user1", "pswd1", "https://a.com/"}},
-                        .shadow_logins = {{"user1", "pswdX", "https://a.com/"}},
-                        .in_common = 1,
-                        .in_main = 0,
-                        .in_shadow = 0,
-                        .inconsistent = 1},
-                    LoginsMetricsParam{
-                        .main_logins = {{"user1", "pswd1", "https://a.com/"}},
-                        .shadow_logins = {},
-                        .in_common = 0,
-                        .in_main = 1,
-                        .in_shadow = 0,
-                        .inconsistent = 0},
-                    LoginsMetricsParam{
-                        .main_logins = {},
-                        .shadow_logins = {{"user1", "pswd1", "https://a.com/"}},
-                        .in_common = 0,
-                        .in_main = 0,
-                        .in_shadow = 1,
-                        .inconsistent = 0},
-                    LoginsMetricsParam{
-                        .main_logins = {{"user4", "pswd4", "https://d.com/"},
-                                        {"user2", "pswd2", "https://b.com/"},
-                                        {"user1", "pswd1", "https://a.com/"}},
-                        .shadow_logins = {{"user1", "pswd1", "https://a.com/"},
-                                          {"user3", "pswd3", "https://c.com/"},
-                                          {"user4", "pswdX", "https://d.com/"}},
-                        .in_common = 2,
-                        .in_main = 1,
-                        .in_shadow = 1,
-                        .inconsistent = 1}));
-
 // Holds the active experiment stage and the expected outcome.
 struct UpmVariationParam {
-  UpmExperimentVariation variation =
-      UpmExperimentVariation::kEnableForSyncingUsers;
   bool is_sync_enabled = false;
   bool calls_android_backend = false;
   bool calls_built_in_backend = false;
@@ -1098,11 +885,6 @@
       public testing::WithParamInterface<UpmVariationParam> {
  public:
   void SetUp() override {
-    feature_list_.InitAndEnableFeatureWithParameters(
-        features::kUnifiedPasswordManagerAndroid,
-        {{"stage",
-          base::NumberToString(static_cast<int>(GetParam().variation))}});
-
     if (GetParam().is_sync_enabled) {
       EnablePasswordSync();
     } else {
@@ -1131,46 +913,17 @@
   proxy_backend().GetAllLoginsAsync(base::DoNothing());
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    PasswordStoreProxyBackendTest,
-    PasswordStoreProxyBackendTestForExperimentStages,
-    testing::Values(
-        UpmVariationParam{
-            .variation = UpmExperimentVariation::kEnableForSyncingUsers,
-            .is_sync_enabled = true,
-            .calls_android_backend = true,
-            .calls_built_in_backend = false,
-        },
-        UpmVariationParam{
-            .variation = UpmExperimentVariation::kEnableForSyncingUsers,
-            .is_sync_enabled = false,
-            .calls_android_backend = false,
-            .calls_built_in_backend = true,
-        },
-        UpmVariationParam{
-            .variation = UpmExperimentVariation::kShadowSyncingUsers,
-            .is_sync_enabled = true,
-            .calls_android_backend = true,  // As shadow traffic.
-            .calls_built_in_backend = true,
-        },
-        UpmVariationParam{
-            .variation = UpmExperimentVariation::kShadowSyncingUsers,
-            .is_sync_enabled = false,
-            .calls_android_backend = false,  // No shadow traffic anymore!
-            .calls_built_in_backend = true,
-        },
-        UpmVariationParam{
-            .variation =
-                UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers,
-            .is_sync_enabled = true,
-            .calls_android_backend = true,
-            .calls_built_in_backend = false,
-        },
-        UpmVariationParam{
-            .variation =
-                UpmExperimentVariation::kEnableOnlyBackendForSyncingUsers,
-            .is_sync_enabled = false,
-            .calls_android_backend = false,
-            .calls_built_in_backend = true,
-        }));
+INSTANTIATE_TEST_SUITE_P(PasswordStoreProxyBackendTest,
+                         PasswordStoreProxyBackendTestForExperimentStages,
+                         testing::Values(
+                             UpmVariationParam{
+                                 .is_sync_enabled = true,
+                                 .calls_android_backend = true,
+                                 .calls_built_in_backend = false,
+                             },
+                             UpmVariationParam{
+                                 .is_sync_enabled = false,
+                                 .calls_android_backend = false,
+                                 .calls_built_in_backend = true,
+                             }));
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_ui_utils.cc b/components/password_manager/core/browser/password_ui_utils.cc
index c27248c0..2d7d4b9 100644
--- a/components/password_manager/core/browser/password_ui_utils.cc
+++ b/components/password_manager/core/browser/password_ui_utils.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
@@ -174,4 +175,9 @@
   return ToUsernameString(base::UTF8ToUTF16(username));
 }
 
+GURL RPIDToURL(const std::string& relying_party_id) {
+  return GURL(base::StrCat(
+      {url::kHttpsScheme, url::kStandardSchemeSeparator, relying_party_id}));
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_ui_utils.h b/components/password_manager/core/browser/password_ui_utils.h
index 37a9b348..e392780 100644
--- a/components/password_manager/core/browser/password_ui_utils.h
+++ b/components/password_manager/core/browser/password_ui_utils.h
@@ -79,6 +79,10 @@
 std::u16string ToUsernameString(const std::u16string& username);
 std::u16string ToUsernameString(const std::string& username);
 
+// Converts a passkey's relying party identifier into an equivalent URL for
+// display.
+GURL RPIDToURL(const std::string& relying_party_id);
+
 }  // namespace password_manager
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_UI_UTILS_H_
diff --git a/components/password_manager/core/browser/sync/password_model_type_controller.cc b/components/password_manager/core/browser/sync/credential_model_type_controller.cc
similarity index 83%
rename from components/password_manager/core/browser/sync/password_model_type_controller.cc
rename to components/password_manager/core/browser/sync/credential_model_type_controller.cc
index a0c55cf..94c547c 100644
--- a/components/password_manager/core/browser/sync/password_model_type_controller.cc
+++ b/components/password_manager/core/browser/sync/credential_model_type_controller.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 "components/password_manager/core/browser/sync/password_model_type_controller.h"
+#include "components/password_manager/core/browser/sync/credential_model_type_controller.h"
 
 #include <utility>
 
@@ -37,7 +37,8 @@
 
 }  // namespace
 
-PasswordModelTypeController::PasswordModelTypeController(
+CredentialModelTypeController::CredentialModelTypeController(
+    syncer::ModelType model_type,
     std::unique_ptr<syncer::ModelTypeControllerDelegate>
         delegate_for_full_sync_mode,
     std::unique_ptr<syncer::ModelTypeControllerDelegate>
@@ -45,7 +46,7 @@
     PrefService* pref_service,
     signin::IdentityManager* identity_manager,
     syncer::SyncService* sync_service)
-    : ModelTypeController(syncer::PASSWORDS,
+    : ModelTypeController(model_type,
                           std::move(delegate_for_full_sync_mode),
                           std::move(delegate_for_transport_mode)),
       pref_service_(pref_service),
@@ -55,14 +56,16 @@
           pref_service_,
           sync_service_,
           base::BindRepeating(
-              &PasswordModelTypeController::OnOptInStateMaybeChanged,
+              &CredentialModelTypeController::OnOptInStateMaybeChanged,
               base::Unretained(this))) {
+  CHECK(model_type == syncer::PASSWORDS ||
+        model_type == syncer::WEBAUTHN_CREDENTIAL);
   identity_manager_observation_.Observe(identity_manager_);
 }
 
-PasswordModelTypeController::~PasswordModelTypeController() = default;
+CredentialModelTypeController::~CredentialModelTypeController() = default;
 
-void PasswordModelTypeController::LoadModels(
+void CredentialModelTypeController::LoadModels(
     const syncer::ConfigureContext& configure_context,
     const ModelLoadCallback& model_load_callback) {
   DCHECK(CalledOnValidThread());
@@ -71,8 +74,8 @@
   ModelTypeController::LoadModels(configure_context, model_load_callback);
 }
 
-void PasswordModelTypeController::Stop(syncer::SyncStopMetadataFate fate,
-                                       StopCallback callback) {
+void CredentialModelTypeController::Stop(syncer::SyncStopMetadataFate fate,
+                                         StopCallback callback) {
   DCHECK(CalledOnValidThread());
   sync_service_observation_.Reset();
   // In transport-only mode, our storage is scoped to the Gaia account. That
@@ -87,7 +90,7 @@
 }
 
 syncer::DataTypeController::PreconditionState
-PasswordModelTypeController::GetPreconditionState() const {
+CredentialModelTypeController::GetPreconditionState() const {
 #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
   // If Sync-the-feature is enabled, then the user has opted in to that, and no
   // additional opt-in is required here.
@@ -95,7 +98,7 @@
       sync_service_->IsLocalSyncEnabled()) {
     return PreconditionState::kPreconditionsMet;
   }
-  // If Sync-the-feature is *not* enabled, then password sync should only be
+  // If Sync-the-feature is *not* enabled, then credential sync should only be
   // turned on if the user has opted in to the account-scoped storage.
   return features_util::IsOptedInForAccountStorage(pref_service_, sync_service_)
              ? PreconditionState::kPreconditionsMet
@@ -108,8 +111,9 @@
 #endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
 }
 
-bool PasswordModelTypeController::ShouldRunInTransportOnlyMode() const {
-  if (!base::FeatureList::IsEnabled(features::kEnablePasswordsAccountStorage)) {
+bool CredentialModelTypeController::ShouldRunInTransportOnlyMode() const {
+  if (type() == syncer::PASSWORDS &&
+      !base::FeatureList::IsEnabled(features::kEnablePasswordsAccountStorage)) {
     return false;
   }
 #if BUILDFLAG(IS_IOS)
@@ -136,12 +140,12 @@
   return true;
 }
 
-void PasswordModelTypeController::OnStateChanged(syncer::SyncService* sync) {
+void CredentialModelTypeController::OnStateChanged(syncer::SyncService* sync) {
   DCHECK(CalledOnValidThread());
-  sync_service_->DataTypePreconditionChanged(syncer::PASSWORDS);
+  sync_service_->DataTypePreconditionChanged(type());
 }
 
-void PasswordModelTypeController::OnAccountsInCookieUpdated(
+void CredentialModelTypeController::OnAccountsInCookieUpdated(
     const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
     const GoogleServiceAuthError& error) {
 #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
@@ -166,13 +170,13 @@
 #endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
 }
 
-void PasswordModelTypeController::OnAccountsCookieDeletedByUserAction() {
+void CredentialModelTypeController::OnAccountsCookieDeletedByUserAction() {
 #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
   features_util::ClearAccountStorageSettingsForAllUsers(pref_service_);
 #endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
 }
 
-void PasswordModelTypeController::OnPrimaryAccountChanged(
+void CredentialModelTypeController::OnPrimaryAccountChanged(
     const signin::PrimaryAccountChangeEvent& event) {
 #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
   if (event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
@@ -188,11 +192,11 @@
 #endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
 }
 
-void PasswordModelTypeController::OnOptInStateMaybeChanged() {
+void CredentialModelTypeController::OnOptInStateMaybeChanged() {
   // Note: This method gets called in many other situations as well, not just
   // when the opt-in state changes, but DataTypePreconditionChanged() is cheap
   // if nothing actually changed, so some spurious calls don't hurt.
-  sync_service_->DataTypePreconditionChanged(syncer::PASSWORDS);
+  sync_service_->DataTypePreconditionChanged(type());
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/sync/password_model_type_controller.h b/components/password_manager/core/browser/sync/credential_model_type_controller.h
similarity index 72%
rename from components/password_manager/core/browser/sync/password_model_type_controller.h
rename to components/password_manager/core/browser/sync/credential_model_type_controller.h
index f4da25090..72ffb3d 100644
--- a/components/password_manager/core/browser/sync/password_model_type_controller.h
+++ b/components/password_manager/core/browser/sync/credential_model_type_controller.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 COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_PASSWORD_MODEL_TYPE_CONTROLLER_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_PASSWORD_MODEL_TYPE_CONTROLLER_H_
+#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_CREDENTIAL_MODEL_TYPE_CONTROLLER_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_CREDENTIAL_MODEL_TYPE_CONTROLLER_H_
 
 #include <memory>
 
@@ -12,6 +12,7 @@
 #include "base/scoped_observation.h"
 #include "components/password_manager/core/browser/password_account_storage_settings_watcher.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/sync/base/model_type.h"
 #include "components/sync/driver/model_type_controller.h"
 #include "components/sync/driver/sync_service_observer.h"
 
@@ -24,12 +25,13 @@
 
 namespace password_manager {
 
-// A class that manages the startup and shutdown of password sync.
-class PasswordModelTypeController : public syncer::ModelTypeController,
-                                    public syncer::SyncServiceObserver,
-                                    public signin::IdentityManager::Observer {
+// A class that manages the startup and shutdown of password & passkey sync.
+class CredentialModelTypeController : public syncer::ModelTypeController,
+                                      public syncer::SyncServiceObserver,
+                                      public signin::IdentityManager::Observer {
  public:
-  PasswordModelTypeController(
+  CredentialModelTypeController(
+      syncer::ModelType model_type,
       std::unique_ptr<syncer::ModelTypeControllerDelegate>
           delegate_for_full_sync_mode,
       std::unique_ptr<syncer::ModelTypeControllerDelegate>
@@ -38,11 +40,11 @@
       signin::IdentityManager* identity_manager,
       syncer::SyncService* sync_service);
 
-  PasswordModelTypeController(const PasswordModelTypeController&) = delete;
-  PasswordModelTypeController& operator=(const PasswordModelTypeController&) =
-      delete;
+  CredentialModelTypeController(const CredentialModelTypeController&) = delete;
+  CredentialModelTypeController& operator=(
+      const CredentialModelTypeController&) = delete;
 
-  ~PasswordModelTypeController() override;
+  ~CredentialModelTypeController() override;
 
   // DataTypeController overrides.
   void LoadModels(const syncer::ConfigureContext& configure_context,
@@ -81,9 +83,9 @@
   base::ScopedObservation<syncer::SyncService, syncer::SyncServiceObserver>
       sync_service_observation_{this};
 
-  base::WeakPtrFactory<PasswordModelTypeController> weak_ptr_factory_{this};
+  base::WeakPtrFactory<CredentialModelTypeController> weak_ptr_factory_{this};
 };
 
 }  // namespace password_manager
 
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_PASSWORD_MODEL_TYPE_CONTROLLER_H_
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_CREDENTIAL_MODEL_TYPE_CONTROLLER_H_
diff --git a/components/password_manager/core/browser/ui/credential_ui_entry.cc b/components/password_manager/core/browser/ui/credential_ui_entry.cc
index 4fc579b..75dd0d8b 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry.cc
+++ b/components/password_manager/core/browser/ui/credential_ui_entry.cc
@@ -4,6 +4,7 @@
 
 #include "components/password_manager/core/browser/ui/credential_ui_entry.h"
 
+#include "base/strings/utf_string_conversions.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
 #include "components/password_manager/core/browser/form_parsing/form_parser.h"
 #include "components/password_manager/core/browser/import/csv_password.h"
@@ -119,6 +120,14 @@
   }
 }
 
+CredentialUIEntry::CredentialUIEntry(const PasskeyCredential& passkey)
+    : is_passkey(true), username(base::UTF8ToUTF16(passkey.username())) {
+  CredentialFacet facet;
+  facet.url = RPIDToURL(passkey.rp_id());
+  facet.signon_realm = facet.url.possibly_invalid_spec();
+  facets.push_back(std::move(facet));
+}
+
 CredentialUIEntry::CredentialUIEntry(const CSVPassword& csv_password,
                                      PasswordForm::Store to_store)
     : username(base::UTF8ToUTF16(csv_password.GetUsername())),
diff --git a/components/password_manager/core/browser/ui/credential_ui_entry.h b/components/password_manager/core/browser/ui/credential_ui_entry.h
index 1c1f64b6..221fbb8 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry.h
+++ b/components/password_manager/core/browser/ui/credential_ui_entry.h
@@ -11,6 +11,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "components/password_manager/core/browser/import/csv_password.h"
+#include "components/password_manager/core/browser/passkey_credential.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -82,6 +83,7 @@
   CredentialUIEntry();
   explicit CredentialUIEntry(const PasswordForm& form);
   explicit CredentialUIEntry(const std::vector<PasswordForm>& forms);
+  explicit CredentialUIEntry(const PasskeyCredential& passkey);
   explicit CredentialUIEntry(
       const CSVPassword& csv_password,
       PasswordForm::Store to_store = PasswordForm::Store::kProfileStore);
@@ -92,6 +94,9 @@
   CredentialUIEntry& operator=(const CredentialUIEntry& other);
   CredentialUIEntry& operator=(CredentialUIEntry&& other);
 
+  // True if this credential is a passkey, false otherwise.
+  bool is_passkey = false;
+
   // List of facets represented by this entry which contains the display name,
   // url and sign-on realm of a credential.
   std::vector<CredentialFacet> facets;
diff --git a/components/password_manager/core/browser/ui/passwords_grouper.cc b/components/password_manager/core/browser/ui/passwords_grouper.cc
index 4333c53..cf89dc2a 100644
--- a/components/password_manager/core/browser/ui/passwords_grouper.cc
+++ b/components/password_manager/core/browser/ui/passwords_grouper.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string_util.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_service.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/passkey_credential.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_list_sorter.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
@@ -59,6 +60,12 @@
       .potentially_invalid_spec();
 }
 
+std::string GetFacetRepresentation(const PasskeyCredential& passkey) {
+  std::string as_url = RPIDToURL(passkey.rp_id()).possibly_invalid_spec();
+  return FacetURI::FromPotentiallyInvalidSpec(as_url)
+      .potentially_invalid_spec();
+}
+
 // An implementation of the disjoint-set data structure
 // (https://en.wikipedia.org/wiki/Disjoint-set_data_structure). This
 // implementation uses the path compression and union by rank optimizations,
@@ -221,6 +228,9 @@
 
 }  // namespace
 
+PasswordsGrouper::Credentials::Credentials() = default;
+PasswordsGrouper::Credentials::~Credentials() = default;
+
 PasswordsGrouper::PasswordsGrouper(AffiliationService* affiliation_service)
     : affiliation_service_(affiliation_service) {
   DCHECK(affiliation_service_);
@@ -230,8 +240,9 @@
 }
 PasswordsGrouper::~PasswordsGrouper() = default;
 
-void PasswordsGrouper::GroupPasswords(std::vector<PasswordForm> forms,
-                                      base::OnceClosure callback) {
+void PasswordsGrouper::GroupCredentials(std::vector<PasswordForm> forms,
+                                        std::vector<PasskeyCredential> passkeys,
+                                        base::OnceClosure callback) {
   // Convert forms to Facets.
   std::vector<FacetURI> facets;
   facets.reserve(forms.size());
@@ -243,9 +254,15 @@
     }
   }
 
-  AffiliationService::GroupsCallback group_callback =
-      base::BindOnce(&PasswordsGrouper::GroupPasswordsImpl,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(forms));
+  // Convert passkey relying party IDs to Facets.
+  for (const auto& passkey : passkeys) {
+    facets.emplace_back(
+        FacetURI::FromPotentiallyInvalidSpec(GetFacetRepresentation(passkey)));
+  }
+
+  AffiliationService::GroupsCallback group_callback = base::BindOnce(
+      &PasswordsGrouper::GroupPasswordsImpl, weak_ptr_factory_.GetWeakPtr(),
+      std::move(forms), std::move(passkeys));
 
   // Before grouping passwords merge related groups. After grouping is finished
   // invoke |callback|.
@@ -258,18 +275,21 @@
 std::vector<AffiliatedGroup>
 PasswordsGrouper::GetAffiliatedGroupsWithGroupingInfo() const {
   std::vector<AffiliatedGroup> affiliated_groups;
-  for (auto const& [group_id, affiliated_group] : map_group_id_to_forms) {
+  for (auto const& [group_id, affiliated_group] :
+       map_group_id_to_credentials_) {
+    // Convert each credential into CredentialUIEntry.
     std::vector<CredentialUIEntry> credentials;
-
-    // Convert each vector<PasswordForm> into CredentialUIEntry.
-    for (auto const& [username_password_key, forms] : affiliated_group) {
+    for (auto const& [username_password_key, forms] : affiliated_group.forms) {
       credentials.emplace_back(forms);
     }
+    for (auto const& passkey : affiliated_group.passkeys) {
+      credentials.emplace_back(passkey);
+    }
 
     // Add branding information to the affiliated group.
     FacetBrandingInfo brandingInfo;
-    auto branding_iterator = map_group_id_to_branding_info.find(group_id);
-    if (branding_iterator != map_group_id_to_branding_info.end()) {
+    auto branding_iterator = map_group_id_to_branding_info_.find(group_id);
+    if (branding_iterator != map_group_id_to_branding_info_.end()) {
       brandingInfo = branding_iterator->second;
     }
     // If the branding information is missing, create a default one with the
@@ -306,18 +326,23 @@
 
 std::vector<CredentialUIEntry> PasswordsGrouper::GetAllCredentials() const {
   std::vector<CredentialUIEntry> credentials;
-  for (const auto& [group_id, affiliated_credentials] : map_group_id_to_forms) {
-    for (const auto& [username_password_key, forms] : affiliated_credentials) {
+  for (const auto& [group_id, affiliated_credentials] :
+       map_group_id_to_credentials_) {
+    for (const auto& [username_password_key, forms] :
+         affiliated_credentials.forms) {
       credentials.emplace_back(forms);
     }
+    for (const auto& passkey : affiliated_credentials.passkeys) {
+      credentials.emplace_back(passkey);
+    }
   }
   return credentials;
 }
 
 std::vector<CredentialUIEntry> PasswordsGrouper::GetBlockedSites() const {
   std::vector<CredentialUIEntry> results;
-  results.reserve(blocked_sites.size());
-  base::ranges::transform(blocked_sites, std::back_inserter(results),
+  results.reserve(blocked_sites_.size());
+  base::ranges::transform(blocked_sites_, std::back_inserter(results),
                           [](const PasswordForm& password_form) {
                             return CredentialUIEntry(password_form);
                           });
@@ -332,7 +357,7 @@
 
   // Verify if the credential is in blocked sites first.
   if (credential.blocked_by_user) {
-    for (const auto& blocked_site : blocked_sites) {
+    for (const auto& blocked_site : blocked_sites_) {
       if (credential.GetFirstSignonRealm() == blocked_site.signon_realm) {
         forms.push_back(blocked_site);
       }
@@ -341,22 +366,22 @@
   }
 
   // Get group id based on signon_realm.
-  auto group_id_iterator = map_signon_realm_to_group_id.find(
+  auto group_id_iterator = map_signon_realm_to_group_id_.find(
       SignonRealm(credential.GetFirstSignonRealm()));
-  if (group_id_iterator == map_signon_realm_to_group_id.end()) {
+  if (group_id_iterator == map_signon_realm_to_group_id_.end()) {
     return {};
   }
 
   // Get all username/password pairs related to this group.
   GroupId group_id = group_id_iterator->second;
-  auto group_iterator = map_group_id_to_forms.find(group_id);
-  if (group_iterator == map_group_id_to_forms.end()) {
+  auto group_iterator = map_group_id_to_credentials_.find(group_id);
+  if (group_iterator == map_group_id_to_credentials_.end()) {
     return {};
   }
 
   // Get all password forms with matching username/password.
   const std::map<UsernamePasswordKey, std::vector<PasswordForm>>&
-      username_to_forms = group_iterator->second;
+      username_to_forms = group_iterator->second.forms;
   auto forms_iterator = username_to_forms.find(
       UsernamePasswordKey(CreateUsernamePasswordSortKey(credential)));
   if (forms_iterator == username_to_forms.end()) {
@@ -367,14 +392,15 @@
 }
 
 void PasswordsGrouper::ClearCache() {
-  map_signon_realm_to_group_id.clear();
-  map_group_id_to_branding_info.clear();
-  map_group_id_to_forms.clear();
-  blocked_sites.clear();
+  map_signon_realm_to_group_id_.clear();
+  map_group_id_to_branding_info_.clear();
+  map_group_id_to_credentials_.clear();
+  blocked_sites_.clear();
 }
 
 void PasswordsGrouper::GroupPasswordsImpl(
     std::vector<PasswordForm> forms,
+    std::vector<PasskeyCredential> passkeys,
     const std::vector<GroupedFacets>& groups) {
   ClearCache();
   // Construct map to keep track of facet URI to group id mapping.
@@ -386,7 +412,7 @@
   for (auto& form : forms) {
     // Do not group blocked by user password forms.
     if (form.blocked_by_user) {
-      blocked_sites.push_back(std::move(form));
+      blocked_sites_.push_back(std::move(form));
       continue;
     }
     std::string facet_uri = GetFacetRepresentation(form);
@@ -395,11 +421,21 @@
     GroupId group_id = map_facet_to_group_id[facet_uri];
 
     // Store group id for sign-on realm.
-    map_signon_realm_to_group_id[SignonRealm(form.signon_realm)] = group_id;
+    map_signon_realm_to_group_id_[SignonRealm(form.signon_realm)] = group_id;
 
     // Store form for username/password key.
     UsernamePasswordKey key(CreateUsernamePasswordSortKey(form));
-    map_group_id_to_forms[group_id][key].push_back(std::move(form));
+    map_group_id_to_credentials_[group_id].forms[key].push_back(
+        std::move(form));
+  }
+
+  for (auto& passkey : passkeys) {
+    // Group passkeys.
+    std::string facet_uri = GetFacetRepresentation(passkey);
+    GroupId group_id = map_facet_to_group_id[facet_uri];
+    map_signon_realm_to_group_id_[SignonRealm(facet_uri)] = group_id;
+    map_group_id_to_credentials_[group_id].passkeys.push_back(
+        std::move(passkey));
   }
 }
 
@@ -420,7 +456,7 @@
     }
 
     // Store branding information for the affiliated group.
-    map_group_id_to_branding_info[unique_group_id] =
+    map_group_id_to_branding_info_[unique_group_id] =
         grouped_facets.branding_info;
 
     // Increment so it is a new id for the next group.
diff --git a/components/password_manager/core/browser/ui/passwords_grouper.h b/components/password_manager/core/browser/ui/passwords_grouper.h
index 7cc1853..000a3a8 100644
--- a/components/password_manager/core/browser/ui/passwords_grouper.h
+++ b/components/password_manager/core/browser/ui/passwords_grouper.h
@@ -8,6 +8,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/passkey_credential.h"
 #include "components/password_manager/core/browser/ui/affiliated_group.h"
 
 namespace password_manager {
@@ -33,9 +34,11 @@
   // credentials are part of the same affiliated group so they will be grouped
   // together.
   // |forms| PasswordForms to be grouped.
+  // |passkeys| Passkey metadata to be grouped.
   // |callback| is called after the grouping is finished.
-  void GroupPasswords(std::vector<PasswordForm> forms,
-                      base::OnceClosure callback);
+  void GroupCredentials(std::vector<PasswordForm> password_forms,
+                        std::vector<PasskeyCredential> passkeys,
+                        base::OnceClosure callback);
 
   // Returns a list of affiliated groups created with the password grouping
   // info.
@@ -59,12 +62,25 @@
   using UsernamePasswordKey =
       base::StrongAlias<class UsernamePasswordKeyTag, std::string>;
 
+  // Holds the set of credentials for a given credential group.
+  struct Credentials {
+    Credentials();
+    ~Credentials();
+
+    // Password forms grouped by username-password keys.
+    std::map<UsernamePasswordKey, std::vector<PasswordForm>> forms;
+
+    // List of passkeys associated to the group.
+    std::vector<PasskeyCredential> passkeys;
+  };
+
   // Returns a map of facet URI to group id. Stores branding information for the
   // affiliated group by updating |map_group_id_to_branding_info|.
   std::map<std::string, GroupId> MapFacetsToGroupId(
       const std::vector<GroupedFacets>& groups);
 
   void GroupPasswordsImpl(std::vector<PasswordForm> forms,
+                          std::vector<PasskeyCredential> passkeys,
                           const std::vector<GroupedFacets>& groups);
 
   void InitializePSLExtensionList(std::vector<std::string> psl_extension_list);
@@ -73,20 +89,19 @@
 
   // Structure used to keep track of the mapping between the credential's
   // sign-on realm and the group id.
-  std::map<SignonRealm, GroupId> map_signon_realm_to_group_id;
+  std::map<SignonRealm, GroupId> map_signon_realm_to_group_id_;
 
   // Structure used to keep track of the mapping between the group id and the
   // grouped facet's branding information.
-  std::map<GroupId, FacetBrandingInfo> map_group_id_to_branding_info;
+  std::map<GroupId, FacetBrandingInfo> map_group_id_to_branding_info_;
 
   // Structure used to keep track of the mapping between a group id and the
-  // grouped by username-password key password forms.
-  std::map<GroupId, std::map<UsernamePasswordKey, std::vector<PasswordForm>>>
-      map_group_id_to_forms;
+  // passwords and passkeys.
+  std::map<GroupId, Credentials> map_group_id_to_credentials_;
 
   // Structure to keep track of the blocked sites by user. They are not grouped
   // into affiliated groups.
-  std::vector<PasswordForm> blocked_sites;
+  std::vector<PasswordForm> blocked_sites_;
 
   // The set of domains that the server uses as an extension to the PSL.
   base::flat_set<std::string> psl_extensions_;
diff --git a/components/password_manager/core/browser/ui/passwords_grouper_unittest.cc b/components/password_manager/core/browser/ui/passwords_grouper_unittest.cc
index 6595f39..0d14be5 100644
--- a/components/password_manager/core/browser/ui/passwords_grouper_unittest.cc
+++ b/components/password_manager/core/browser/ui/passwords_grouper_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/test/gmock_callback_support.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
 #include "components/password_manager/core/browser/affiliation/mock_affiliation_service.h"
+#include "components/password_manager/core/browser/passkey_credential.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_list_sorter.h"
 #include "components/password_manager/core/browser/password_ui_utils.h"
@@ -27,6 +28,14 @@
 
 namespace {
 
+PasskeyCredential CreatePasskey(std::string rp_id,
+                                std::string username = "username",
+                                std::string display_name = "display_name") {
+  return PasskeyCredential(PasskeyCredential::Source::kAndroidPhone,
+                           std::move(rp_id), {1, 2, 3, 4}, {5, 6, 7, 8},
+                           std::move(username), std::move(display_name));
+}
+
 PasswordForm CreateForm(std::string signon_realm,
                         std::u16string username = u"username",
                         std::u16string password = u"password") {
@@ -73,6 +82,37 @@
   PasswordsGrouper grouper_{&affiliation_service_};
 };
 
+TEST_F(PasswordsGrouperTest, GetAllCredentials) {
+  PasswordForm form = CreateForm("https://test.com/");
+
+  PasswordForm blocked_form;
+  blocked_form.signon_realm = form.signon_realm;
+  blocked_form.blocked_by_user = true;
+
+  PasswordForm federated_form;
+  federated_form.url = GURL("https://test.com/");
+  federated_form.signon_realm = "federation://test.com/accounts.federation.com";
+  federated_form.username_value = u"username2";
+  federated_form.federation_origin =
+      url::Origin::Create(GURL("https://test.com"));
+
+  GroupedFacets group;
+  group.facets = {
+      Facet(FacetURI::FromPotentiallyInvalidSpec("https://test.com"))};
+  EXPECT_CALL(affiliation_service(), GetGroupingInfo)
+      .WillRepeatedly(base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{
+          std::move(group), GetSingleGroupForForm(form)}));
+
+  PasskeyCredential passkey = CreatePasskey("test.com");
+  grouper().GroupCredentials({form, blocked_form, federated_form}, {passkey},
+                             base::DoNothing());
+
+  EXPECT_THAT(grouper().GetAllCredentials(),
+              UnorderedElementsAre(CredentialUIEntry(form),
+                                   CredentialUIEntry(federated_form),
+                                   CredentialUIEntry(passkey)));
+}
+
 TEST_F(PasswordsGrouperTest, GetAffiliatedGroupsWithGroupingInfo) {
   PasswordForm form = CreateForm("https://test.com/");
 
@@ -98,8 +138,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo(facets, testing::_))
       .WillRepeatedly(base::test::RunOnceCallback<1>(
           std::vector<GroupedFacets>{group, GetSingleGroupForForm(form)}));
-  grouper().GroupPasswords({form, federated_form, blocked_form},
-                           base::DoNothing());
+  grouper().GroupCredentials({form, federated_form, blocked_form},
+                             /*passkeys=*/{}, base::DoNothing());
 
   CredentialUIEntry credential1(form), credential2(federated_form);
   EXPECT_THAT(
@@ -144,8 +184,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(
           std::vector<GroupedFacets>{group, federated_group}));
-  grouper().GroupPasswords({form1, form2, blocked_form, federated_form},
-                           base::DoNothing());
+  grouper().GroupCredentials({form1, form2, blocked_form, federated_form},
+                             /*passkeys=*/{}, base::DoNothing());
 
   CredentialUIEntry credential1(form1), credential2(form2),
       credential3(federated_form);
@@ -160,7 +200,7 @@
               ElementsAre(CredentialUIEntry(blocked_form)));
 }
 
-TEST_F(PasswordsGrouperTest, GroupPasswordsWithoutAffiliation) {
+TEST_F(PasswordsGrouperTest, GroupCredentialsWithoutAffiliation) {
   // Credentials saved for the same website should appear in the same group.
   PasswordForm form1 = CreateForm("https://test.com/");
   PasswordForm form2 =
@@ -184,8 +224,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{
           federated_group, GetSingleGroupForForm(form1)}));
-  grouper().GroupPasswords({form1, form2, blocked_form, federated_form},
-                           base::DoNothing());
+  grouper().GroupCredentials({form1, form2, blocked_form, federated_form},
+                             /*passkeys=*/{}, base::DoNothing());
 
   CredentialUIEntry credential1(form1), credential2(form2),
       credential3(federated_form);
@@ -210,7 +250,7 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(
           base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{group}));
-  grouper().GroupPasswords({form}, base::DoNothing());
+  grouper().GroupCredentials({form}, /*passkeys=*/{}, base::DoNothing());
 
   CredentialUIEntry credential(form);
   EXPECT_THAT(grouper().GetAffiliatedGroupsWithGroupingInfo(),
@@ -232,7 +272,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(
           std::vector<GroupedFacets>{GetSingleGroupForForm(form)}));
-  grouper().GroupPasswords({form, federated_form}, base::DoNothing());
+  grouper().GroupCredentials({form, federated_form}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential(form);
   EXPECT_THAT(grouper().GetAffiliatedGroupsWithGroupingInfo(),
@@ -241,6 +282,22 @@
                   GetDefaultBrandingInfo(credential))));
 }
 
+TEST_F(PasswordsGrouperTest, PasskeysGroupedWithPasswords) {
+  PasswordForm form = CreateForm("https://test.com/");
+  PasskeyCredential passkey = CreatePasskey("test.com");
+
+  EXPECT_CALL(affiliation_service(), GetGroupingInfo)
+      .WillRepeatedly(base::test::RunOnceCallback<1>(
+          std::vector<GroupedFacets>{GetSingleGroupForForm(form)}));
+  grouper().GroupCredentials({form}, {passkey}, base::DoNothing());
+
+  CredentialUIEntry credential(form);
+  EXPECT_THAT(
+      grouper().GetAffiliatedGroupsWithGroupingInfo(),
+      ElementsAre(AffiliatedGroup({credential, CredentialUIEntry(passkey)},
+                                  {GetDefaultBrandingInfo(credential)})));
+}
+
 TEST_F(PasswordsGrouperTest, GroupsWithMatchingMainDomainsMerged) {
   std::vector<PasswordForm> forms = {CreateForm("https://m.a.com/", u"test1"),
                                      CreateForm("https://a.com/", u"test2"),
@@ -267,7 +324,7 @@
 
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(grouped_facets));
-  grouper().GroupPasswords(forms, base::DoNothing());
+  grouper().GroupCredentials(forms, /*passkeys=*/{}, base::DoNothing());
 
   CredentialUIEntry credential1(forms[0]), credential2(forms[1]),
       credential3(forms[2]), credential4(forms[3]);
@@ -302,7 +359,7 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(grouped_facets));
 
-  grouper.GroupPasswords(forms, base::DoNothing());
+  grouper.GroupCredentials(forms, /*passkeys=*/{}, base::DoNothing());
 
   CredentialUIEntry credential1(forms[0]), credential2(forms[1]),
       credential3(forms[2]), credential4(forms[3]);
@@ -332,7 +389,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(
           std::vector<GroupedFacets>{group, GetSingleGroupForForm(form2)}));
-  grouper().GroupPasswords({form1, form2}, base::DoNothing());
+  grouper().GroupCredentials({form1, form2}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential({form1, form2});
   EXPECT_THAT(grouper().GetAffiliatedGroupsWithGroupingInfo(),
@@ -366,7 +424,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(
           base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{group}));
-  grouper().GroupPasswords({form, federated_android_form}, base::DoNothing());
+  grouper().GroupCredentials({form, federated_android_form}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential({form}),
       federated_credential({federated_android_form});
@@ -399,7 +458,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(
           base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{group}));
-  grouper().GroupPasswords({form, federated_form}, base::DoNothing());
+  grouper().GroupCredentials({form, federated_form}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential1(form), credential2(federated_form);
   EXPECT_THAT(
@@ -428,7 +488,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(base::test::RunOnceCallback<1>(
           std::vector<GroupedFacets>{group1, group2, group3}));
-  grouper().GroupPasswords({form1, form2, form3}, base::DoNothing());
+  grouper().GroupCredentials({form1, form2, form3}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential1(form1), credential2(form2), credential3(form3);
   EXPECT_THAT(
@@ -453,7 +514,8 @@
   EXPECT_CALL(affiliation_service(), GetGroupingInfo)
       .WillRepeatedly(
           base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{group}));
-  grouper().GroupPasswords({form1, form2, form3, form4}, base::DoNothing());
+  grouper().GroupCredentials({form1, form2, form3, form4}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential1({form1, form2}), credential2(form3),
       credential3(form4);
@@ -472,7 +534,8 @@
       .WillRepeatedly(base::test::RunOnceCallback<1>(std::vector<GroupedFacets>{
           GetSingleGroupForForm(form1), GetSingleGroupForForm(form2),
           GetSingleGroupForForm(ip_form)}));
-  grouper().GroupPasswords({form1, form2, ip_form}, base::DoNothing());
+  grouper().GroupCredentials({form1, form2, ip_form}, /*passkeys=*/{},
+                             base::DoNothing());
 
   CredentialUIEntry credential1(form1), credential2(form2),
       credential3(ip_form);
diff --git a/components/password_manager/core/browser/ui/saved_passwords_presenter.cc b/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
index 3f7b31f..5ea26511 100644
--- a/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
+++ b/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
@@ -601,8 +601,8 @@
   }
 
   // Notify observers after grouping is complete.
-  passwords_grouper_->GroupPasswords(
-      std::move(all_forms),
+  passwords_grouper_->GroupCredentials(
+      std::move(all_forms), /*passkeys=*/{},
       metrics_util::TimeCallback(std::move(completion),
                                  "PasswordManager.PasswordsGrouping.Time"));
 }
diff --git a/components/pdf/renderer/pdf_accessibility_tree.cc b/components/pdf/renderer/pdf_accessibility_tree.cc
index d56da1e..15f1330 100644
--- a/components/pdf/renderer/pdf_accessibility_tree.cc
+++ b/components/pdf/renderer/pdf_accessibility_tree.cc
@@ -528,7 +528,8 @@
           node_id_to_annotation_info
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
       ,
-      PdfOcrService* ocr_service
+      PdfOcrService* ocr_service,
+      bool has_accessible_text
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
       )
       : pdf_accessibility_tree_(std::move(pdf_accessibility_tree)),
@@ -548,7 +549,8 @@
         node_id_to_annotation_info_(node_id_to_annotation_info)
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
         ,
-        ocr_service_(ocr_service)
+        ocr_service_(ocr_service),
+        has_accessible_text_(has_accessible_text)
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   {
     page_node_ = CreateAndAppendNode(ax::mojom::Role::kRegion,
@@ -1278,7 +1280,8 @@
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
       // TODO(crbug.com/1278249): Consider moving images instead of copying
       // them.
-      if (ocr_available && !images_[i].image_data.drawsNothing()) {
+      if (!has_accessible_text_ && ocr_available &&
+          !images_[i].image_data.drawsNothing()) {
         ocr_requests.emplace(image_node->id, images_[i], para_node->id);
       }
 #endif
@@ -1347,6 +1350,7 @@
 
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   PdfOcrService* ocr_service_ = nullptr;
+  const bool has_accessible_text_;
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 };
 
@@ -1675,7 +1679,7 @@
       &node_id_to_page_char_index_, &node_id_to_annotation_info_
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
       ,
-      ocr_service_.get()
+      ocr_service_.get(), did_get_a_text_run_
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   );
   tree_builder.BuildPageTree();
@@ -1716,6 +1720,14 @@
 
   base::UmaHistogramBoolean("Accessibility.PDF.HasAccessibleText",
                             did_get_a_text_run_);
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+  if (!did_get_a_text_run_) {
+    // TODO(crbug.com/1443345): Add a browser test to validate this UMA metric.
+    base::UmaHistogramBoolean(
+        "Accessibility.PdfOcr.ActiveWhenInaccessiblePdfOpened",
+        ocr_service_ != nullptr);
+  }
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 }
 
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index 68019d7..9343d5c 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -53,28 +53,32 @@
              "HeuristicMemorySaver",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// If 0, uses a default value from heuristic_memory_saver_policy.cc.
 const base::FeatureParam<base::TimeDelta>
     kHeuristicMemorySaverThresholdReachedHeartbeatInterval{
         &kHeuristicMemorySaver, "threshold_reached_heartbeat_interval",
-        base::Seconds(10)};
+        base::TimeDelta()};
 const base::FeatureParam<base::TimeDelta>
     kHeuristicMemorySaverThresholdNotReachedHeartbeatInterval{
         &kHeuristicMemorySaver, "threshold_not_reached_heartbeat_interval",
-        base::Seconds(60)};
+        base::TimeDelta()};
+const base::FeatureParam<base::TimeDelta>
+    kHeuristicMemorySaverMinimumTimeInBackground{&kHeuristicMemorySaver,
+                                                 "minimum_time_in_background",
+                                                 base::TimeDelta()};
 
+// If < 0, uses a default value from heuristic_memory_saver_policy.cc.
 const base::FeatureParam<int>
     kHeuristicMemorySaverAvailableMemoryThresholdPercent{
-        &kHeuristicMemorySaver, "threshold_percent", 5};
-
+        &kHeuristicMemorySaver, "threshold_percent", -1};
 const base::FeatureParam<int> kHeuristicMemorySaverAvailableMemoryThresholdMb{
-    &kHeuristicMemorySaver, "threshold_mb", 4096};
-
+    &kHeuristicMemorySaver, "threshold_mb", -1};
 const base::FeatureParam<int> kHeuristicMemorySaverPageCacheDiscountMac{
-    &kHeuristicMemorySaver, "mac_page_cache_available_percent", 50};
+    &kHeuristicMemorySaver, "mac_page_cache_available_percent", -1};
 
-const base::FeatureParam<base::TimeDelta>
-    kHeuristicMemorySaverMinimumTimeInBackground{
-        &kHeuristicMemorySaver, "minimum_time_in_background", base::Hours(2)};
+BASE_FEATURE(kForceHeuristicMemorySaver,
+             "ForceHeuristicMemorySaver",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kHighEfficiencyMultistateMode,
              "HighEfficiencyMultistateMode",
diff --git a/components/performance_manager/performance_manager_tab_helper.h b/components/performance_manager/performance_manager_tab_helper.h
index 6cfd2e0..0a160e3 100644
--- a/components/performance_manager/performance_manager_tab_helper.h
+++ b/components/performance_manager/performance_manager_tab_helper.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/containers/flat_set.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index 54bcb60c..5bd08e57 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -54,7 +54,19 @@
 extern const base::FeatureParam<base::TimeDelta>
     kPerformanceControlsBatterySurveyLookback;
 
-// When enabled, the memory saver policy used is HeuristicMemorySaverPolicy.
+// Controls whether HeuristicMemorySaverPolicy is used as the memory saver
+// policy:
+//
+// * If neither this nor kHighEfficiencyMultistateMode are enabled, the
+//   timer-based memory saver policy is used.
+// * If this is enabled but kHighEfficiencyMultistateMode is not, the heuristic
+//   memory saver is used, with parameters set from the feature params.
+// * If this and kHighEfficiencyMultistateMode are both enabled, the policy
+//   that's used is controlled by the multistate UI, and the heuristic policy
+//   parameters are set from the feature params.
+// * If kHighEfficiencyMultistateMode is enabled and this is not, heuristic
+//   memory saver is enabled if chosen by the user, but the policy params use
+//   default values.
 BASE_DECLARE_FEATURE(kHeuristicMemorySaver);
 
 // Controls the interval at which HeuristicMemorySaverPolicy checks whether the
@@ -101,6 +113,21 @@
 extern const base::FeatureParam<base::TimeDelta>
     kHeuristicMemorySaverMinimumTimeInBackground;
 
+// If enabled, the kHeuristicMemorySaver feature controls the memory saver
+// policy regardless of the state of the UI:
+//
+// * If kHeuristicMemorySaver is enabled, HeuristicMemorySaverPolicy will be
+//   turned on.
+// * If kHeuristicMemorySaver is disabled, all memory saver policies will be
+//   turned off (for the control group of experiemnts).
+//
+// Note: to get uniform control and experiment groups,
+// kForceHeuristicMemorySaver should only be tested for users who are valid for
+// randomized studies (eg. not for users with the HighEfficiencyModeState pref
+// managed by enterprise policy). It's always safe to read kHeuristicMemorySaver
+// without kForceHeuristicMemorySaver to get the policy params.
+BASE_DECLARE_FEATURE(kForceHeuristicMemorySaver);
+
 // Round 2 Performance Controls features
 
 // This enables the UI for the multi-state version of high efficiency mode.
diff --git a/components/permissions/permission_request_manager.h b/components/permissions/permission_request_manager.h
index a0da5da..4d7ff6e 100644
--- a/components/permissions/permission_request_manager.h
+++ b/components/permissions/permission_request_manager.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/check_is_test.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/time/time.h"
diff --git a/components/policy/core/browser/url_scheme_list_policy_handler.h b/components/policy/core/browser/url_scheme_list_policy_handler.h
index 15a3d025..451813d 100644
--- a/components/policy/core/browser/url_scheme_list_policy_handler.h
+++ b/components/policy/core/browser/url_scheme_list_policy_handler.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_POLICY_CORE_BROWSER_URL_SCHEME_LIST_POLICY_HANDLER_H_
 
 #include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
 #include "components/policy/policy_export.h"
 
diff --git a/components/policy/tools/generate_policy_source.py b/components/policy/tools/generate_policy_source.py
index 30355d01..05976fd4c 100755
--- a/components/policy/tools/generate_policy_source.py
+++ b/components/policy/tools/generate_policy_source.py
@@ -980,7 +980,7 @@
       f.write('};\n\n')
 
     f.write('const internal::SchemaData* GetChromeSchemaData() {\n')
-    f.write('  static constexpr internal::SchemaData chrome_schema_data = {\n'
+    f.write('  static const internal::SchemaData kChromeSchemaData = {\n'
             '    kSchemas,\n')
     f.write('    kPropertyNodes,\n' if self.property_nodes else '  nullptr,\n')
     f.write('    kProperties,\n' if self.properties_nodes else '  nullptr,\n')
@@ -994,7 +994,7 @@
     f.write('    %d,  // validation_schema root index\n' %
             self.validation_schema_root_index)
     f.write('  };\n\n')
-    f.write('  return &chrome_schema_data;\n' '}\n\n')
+    f.write('  return &kChromeSchemaData;\n' '}\n\n')
 
   def GetByID(self, id_str):
     if not isinstance(id_str, string_type):
diff --git a/components/policy/tools/generate_policy_source_test_data.py b/components/policy/tools/generate_policy_source_test_data.py
index cc90ea5d..c71b20c8 100644
--- a/components/policy/tools/generate_policy_source_test_data.py
+++ b/components/policy/tools/generate_policy_source_test_data.py
@@ -660,7 +660,7 @@
 };
 
 const internal::SchemaData* GetChromeSchemaData() {
-  static constexpr internal::SchemaData chrome_schema_data = {
+  static const internal::SchemaData kChromeSchemaData = {
     kSchemas,
     kPropertyNodes,
     kProperties,
@@ -671,7 +671,7 @@
     -1,  // validation_schema root index
   };
 
-  return &chrome_schema_data;
+  return &kChromeSchemaData;
 }
 
 
diff --git a/components/power_bookmarks/storage/power_bookmark_database_impl.h b/components/power_bookmarks/storage/power_bookmark_database_impl.h
index 0414f13..5f4eabb 100644
--- a/components/power_bookmarks/storage/power_bookmark_database_impl.h
+++ b/components/power_bookmarks/storage/power_bookmark_database_impl.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_POWER_BOOKMARKS_STORAGE_POWER_BOOKMARK_DATABASE_IMPL_H_
 
 #include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
 #include "components/power_bookmarks/common/power.h"
 #include "components/power_bookmarks/common/power_overview.h"
 #include "components/power_bookmarks/storage/power_bookmark_database.h"
diff --git a/components/power_metrics/m1_sensors_mac.mm b/components/power_metrics/m1_sensors_mac.mm
index 9c32756..10b5c50e 100644
--- a/components/power_metrics/m1_sensors_mac.mm
+++ b/components/power_metrics/m1_sensors_mac.mm
@@ -11,7 +11,7 @@
 
 #include <utility>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/memory/ptr_util.h"
 #include "components/power_metrics/m1_sensors_internal_types_mac.h"
@@ -66,7 +66,7 @@
     @kIOHIDPrimaryUsagePageKey : @(kHIDPage_AppleVendor),
     @kIOHIDPrimaryUsageKey : @(kHIDUsage_AppleVendor_TemperatureSensor),
   };
-  IOHIDEventSystemClientSetMatching(system, base::mac::NSToCFPtrCast(filter));
+  IOHIDEventSystemClientSetMatching(system, base::apple::NSToCFPtrCast(filter));
 
   return base::WrapUnique(new M1SensorsReader(std::move(system)));
 }
diff --git a/components/safe_browsing/content/renderer/renderer_url_loader_throttle.h b/components/safe_browsing/content/renderer/renderer_url_loader_throttle.h
index b2561dc..05b51cd 100644
--- a/components/safe_browsing/content/renderer/renderer_url_loader_throttle.h
+++ b/components/safe_browsing/content/renderer/renderer_url_loader_throttle.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "components/safe_browsing/content/common/safe_browsing.mojom.h"
diff --git a/components/safe_browsing/core/browser/db/hash_prefix_map.cc b/components/safe_browsing/core/browser/db/hash_prefix_map.cc
index cc039997..4936357 100644
--- a/components/safe_browsing/core/browser/db/hash_prefix_map.cc
+++ b/components/safe_browsing/core/browser/db/hash_prefix_map.cc
@@ -66,7 +66,7 @@
       : prefix_size_(prefix_size) {}
 
   void Reserve(size_t size) {
-    offsets_.resize(GetOffsetMapSize(size), kInvalidOffset);
+    offsets_.Resize(GetOffsetMapSize(size), kInvalidOffset);
   }
 
   // Add() may be called in two situations:
@@ -95,13 +95,15 @@
     }
   }
 
-  std::vector<uint32_t> TakeOffsets() {
+  google::protobuf::RepeatedField<uint32_t> TakeOffsets() {
     // Backfill any empty spots with the value right after it.
     uint32_t last = cur_offset_;
     for (int i = offsets_.size() - 1; i >= 0; i--) {
-      if (offsets_[i] == kInvalidOffset)
+      if (offsets_[i] == kInvalidOffset) {
         offsets_[i] = last;
-      last = offsets_[i];
+      } else {
+        last = offsets_[i];
+      }
     }
     return std::move(offsets_);
   }
@@ -110,7 +112,7 @@
 
  private:
   const PrefixSize prefix_size_;
-  std::vector<uint32_t> offsets_;
+  google::protobuf::RepeatedField<uint32_t> offsets_;
   size_t cur_offset_ = 0;
 };
 
@@ -302,7 +304,9 @@
 
   void Reserve(size_t size) { offset_builder_.Reserve(size); }
 
-  std::vector<uint32_t> TakeOffsets() { return offset_builder_.TakeOffsets(); }
+  google::protobuf::RepeatedField<uint32_t> TakeOffsets() {
+    return offset_builder_.TakeOffsets();
+  }
 
   size_t GetFileSize() const { return offset_builder_.GetFileSize(); }
 
@@ -435,8 +439,7 @@
       return MigrateResult::kFailure;
 
     builder.Add(info.GetView());
-    auto offsets = builder.TakeOffsets();
-    hash_file.mutable_offsets()->Assign(offsets.begin(), offsets.end());
+    *hash_file.mutable_offsets() = builder.TakeOffsets();
     offsets_updated = true;
   }
 
@@ -516,8 +519,7 @@
 
   hash_file->set_prefix_size(prefix_size_);
 
-  auto offsets = writer_->TakeOffsets();
-  hash_file->mutable_offsets()->Assign(offsets.begin(), offsets.end());
+  *hash_file->mutable_offsets() = writer_->TakeOffsets();
   hash_file->set_file_size(writer_->GetFileSize());
   hash_file->set_extension(writer_->extension());
   writer_.reset();
diff --git a/components/safe_browsing/core/browser/db/v4_database.cc b/components/safe_browsing/core/browser/db/v4_database.cc
index 06c79cc..a613814 100644
--- a/components/safe_browsing/core/browser/db/v4_database.cc
+++ b/components/safe_browsing/core/browser/db/v4_database.cc
@@ -99,11 +99,16 @@
   if (!g_store_factory.Get())
     g_store_factory.Get() = std::make_unique<V4StoreFactory>();
 
-  SCOPED_CRASH_KEY_STRING256("SafeBrowsing", "database-path",
-                             base_path.AsUTF8Unsafe());
-
-  if (!base::CreateDirectory(base_path))
+  // TODO(crbug/1434333): This is being used temporarily to investigate why this
+  // NOTREACHED is being triggered.
+  base::File::Error error = base::File::FILE_OK;
+  bool success = base::CreateDirectoryAndGetError(base_path, &error);
+  base::UmaHistogramExactLinear(
+      "SafeBrowsing.V4Database.DirectoryCreationResult", -error,
+      -base::File::FILE_ERROR_MAX);
+  if (!success) {
     NOTREACHED();
+  }
 
 #if BUILDFLAG(IS_APPLE)
   base::mac::SetBackupExclusion(base_path);
diff --git a/components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h b/components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h
index 6295a5c..f6eafd12 100644
--- a/components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h
+++ b/components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_SAFE_BROWSING_CORE_BROWSER_HASHPREFIX_REALTIME_HASH_REALTIME_SERVICE_H_
 
 #include "base/containers/unique_ptr_adapters.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
diff --git a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h
index 322fe18..b8bbf58 100644
--- a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h
+++ b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_SAFE_BROWSING_CORE_BROWSER_SAFE_BROWSING_LOOKUP_MECHANISM_EXPERIMENTER_H_
 #define COMPONENTS_SAFE_BROWSING_CORE_BROWSER_SAFE_BROWSING_LOOKUP_MECHANISM_EXPERIMENTER_H_
 
+#include "base/gtest_prod_util.h"
 #include "components/safe_browsing/core/browser/db/database_manager.h"
 #include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h"
diff --git a/components/safe_browsing/core/browser/sync/safe_browsing_sync_observer_impl_unittest.cc b/components/safe_browsing/core/browser/sync/safe_browsing_sync_observer_impl_unittest.cc
index def9d92..8e7b8a0 100644
--- a/components/safe_browsing/core/browser/sync/safe_browsing_sync_observer_impl_unittest.cc
+++ b/components/safe_browsing/core/browser/sync/safe_browsing_sync_observer_impl_unittest.cc
@@ -18,7 +18,7 @@
 
   void SetUp() override {
     sync_service_.SetDisableReasons(
-        syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN);
+        {syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN});
   }
 
  protected:
@@ -29,7 +29,7 @@
 
   void DisableSync() {
     sync_service_.SetDisableReasons(
-        syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN);
+        {syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN});
     sync_service_.FireStateChanged();
   }
 
diff --git a/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc b/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc
index 677da3a..f8e32a5 100644
--- a/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc
+++ b/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc
@@ -121,8 +121,7 @@
   // return true.
   sync_service.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kHistory));
+      /*types=*/{syncer::UserSelectableType::kHistory});
 
   EXPECT_TRUE(SyncUtils::IsHistorySyncEnabled(&sync_service));
 
@@ -134,8 +133,7 @@
   // History is not being synced.
   sync_service.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kAutofill));
+      /*types=*/{syncer::UserSelectableType::kAutofill});
   EXPECT_FALSE(SyncUtils::IsHistorySyncEnabled(&sync_service));
 
   sync_service.GetUserSettings()->SetSelectedTypes(
diff --git a/components/services/quarantine/quarantine_mac.mm b/components/services/quarantine/quarantine_mac.mm
index 88667f4..226dd65 100644
--- a/components/services/quarantine/quarantine_mac.mm
+++ b/components/services/quarantine/quarantine_mac.mm
@@ -7,10 +7,10 @@
 #import <ApplicationServices/ApplicationServices.h>
 #import <Foundation/Foundation.h>
 
+#include "base/apple/bridging.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
 #include "base/mac/mac_util.h"
@@ -66,7 +66,7 @@
   }
 
   base::ScopedCFTypeRef<MDItemRef> md_item(
-      MDItemCreate(kCFAllocatorDefault, base::mac::NSToCFPtrCast(file_path)));
+      MDItemCreate(kCFAllocatorDefault, base::apple::NSToCFPtrCast(file_path)));
   if (!md_item) {
     LOG(WARNING) << "MDItemCreate failed for path " << file.value();
     return false;
@@ -89,7 +89,7 @@
 
   if (list.count) {
     return MDItemSetAttribute(md_item, kMDItemWhereFroms,
-                              base::mac::NSToCFPtrCast(list));
+                              base::apple::NSToCFPtrCast(list));
   }
 
   return true;
diff --git a/components/services/quarantine/quarantine_mac_unittest.mm b/components/services/quarantine/quarantine_mac_unittest.mm
index 29306fd6..e6b1772 100644
--- a/components/services/quarantine/quarantine_mac_unittest.mm
+++ b/components/services/quarantine/quarantine_mac_unittest.mm
@@ -7,11 +7,11 @@
 #import <ApplicationServices/ApplicationServices.h>
 #import <Foundation/Foundation.h>
 
+#include "base/apple/bridging.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/run_loop.h"
@@ -169,7 +169,7 @@
   NSString* file_path = base::mac::FilePathToNSString(test_file_);
   ASSERT_NE(nullptr, file_path);
   base::ScopedCFTypeRef<MDItemRef> md_item(
-      MDItemCreate(kCFAllocatorDefault, base::mac::NSToCFPtrCast(file_path)));
+      MDItemCreate(kCFAllocatorDefault, base::apple::NSToCFPtrCast(file_path)));
   if (!md_item) {
     // The quarantine code ignores it if adding origin metadata fails. If for
     // some reason MDItemCreate fails (which it seems to do on the bots, not
diff --git a/components/services/quarantine/test_support_mac.mm b/components/services/quarantine/test_support_mac.mm
index 96e947d..96c70dc 100644
--- a/components/services/quarantine/test_support_mac.mm
+++ b/components/services/quarantine/test_support_mac.mm
@@ -7,9 +7,9 @@
 #import <ApplicationServices/ApplicationServices.h>
 #import <Foundation/Foundation.h>
 
+#include "base/apple/bridging.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -46,7 +46,7 @@
 
   // The agent bundle id must always be set.
   NSString* bundle_id =
-      [properties valueForKey:base::mac::CFToNSPtrCast(
+      [properties valueForKey:base::apple::CFToNSPtrCast(
                                   kLSQuarantineAgentBundleIdentifierKey)];
   if (!bundle_id.length) {
     return false;
@@ -56,7 +56,7 @@
   GURL expected_source_url =
       SanitizeUrlForQuarantine(expected_source_url_unsafe);
   NSString* source_url = [[properties
-      valueForKey:base::mac::CFToNSPtrCast(kLSQuarantineDataURLKey)]
+      valueForKey:base::apple::CFToNSPtrCast(kLSQuarantineDataURLKey)]
       description];
   if (expected_source_url.is_valid() && source_url.length) {
     if (![source_url isEqualToString:base::SysUTF8ToNSString(
@@ -68,7 +68,7 @@
   GURL expected_referrer_url =
       SanitizeUrlForQuarantine(expected_referrer_url_unsafe);
   NSString* referrer_url = [[properties
-      valueForKey:base::mac::CFToNSPtrCast(kLSQuarantineOriginURLKey)]
+      valueForKey:base::apple::CFToNSPtrCast(kLSQuarantineOriginURLKey)]
       description];
   if (expected_referrer_url.is_valid() && referrer_url.length) {
     if (![referrer_url isEqualToString:base::SysUTF8ToNSString(
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h
index 1931af51..4da6fea 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h
@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
diff --git a/components/supervised_user/core/browser/kids_chrome_management_client.cc b/components/supervised_user/core/browser/kids_chrome_management_client.cc
index 1aa82b1..5d21d20 100644
--- a/components/supervised_user/core/browser/kids_chrome_management_client.cc
+++ b/components/supervised_user/core/browser/kids_chrome_management_client.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/escape.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "components/google/core/common/google_util.h"
@@ -294,8 +295,8 @@
 
   req->resource_request->headers.SetHeader(
       net::HttpRequestHeaders::kAuthorization,
-      base::StringPrintf(supervised_user::kAuthorizationHeaderFormat,
-                         token_info.token.c_str()));
+      base::JoinString(
+          {supervised_user::kAuthorizationHeader, token_info.token}, " "));
 
   std::string request_data;
   // TODO(crbug.com/980273): remove this when experiment flag is fully flipped.
diff --git a/components/supervised_user/core/browser/kids_external_fetcher.cc b/components/supervised_user/core/browser/kids_external_fetcher.cc
index 1c9f52c..ae6b609 100644
--- a/components/supervised_user/core/browser/kids_external_fetcher.cc
+++ b/components/supervised_user/core/browser/kids_external_fetcher.cc
@@ -73,6 +73,12 @@
   return loader.ResponseInfo()->headers->response_code();
 }
 
+std::string CreateAuthorizationHeader(StringPiece access_token) {
+  // Do not use StringPiece with StringPrintf, see crbug/1444165
+  return base::JoinString({supervised_user::kAuthorizationHeader, access_token},
+                          " ");
+}
+
 // TODO(b/276898959): Support payload for POST requests.
 std::unique_ptr<network::SimpleURLLoader> InitializeSimpleUrlLoader(
     StringPiece access_token,
@@ -83,10 +89,8 @@
   resource_request->url = url;
   resource_request->method = fetcher_config.GetHttpMethod();
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
-  resource_request->headers.SetHeader(
-      net::HttpRequestHeaders::kAuthorization,
-      base::StringPrintf(supervised_user::kAuthorizationHeaderFormat,
-                         access_token));
+  resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
+                                      CreateAuthorizationHeader(access_token));
   std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
       network::SimpleURLLoader::Create(std::move(resource_request),
                                        fetcher_config.traffic_annotation());
diff --git a/components/supervised_user/core/browser/kids_external_fetcher_unittest.cc b/components/supervised_user/core/browser/kids_external_fetcher_unittest.cc
index 7198001..5948954 100644
--- a/components/supervised_user/core/browser/kids_external_fetcher_unittest.cc
+++ b/components/supervised_user/core/browser/kids_external_fetcher_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <string>
 
+#include "base/strings/string_piece.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/types/expected.h"
@@ -15,6 +16,7 @@
 #include "components/supervised_user/core/browser/kids_external_fetcher_config.h"
 #include "components/supervised_user/core/browser/proto/kidschromemanagement_messages.pb.h"
 #include "google_apis/gaia/google_service_auth_error.h"
+#include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -152,6 +154,36 @@
             KidsExternalFetcherStatus::State::INVALID_RESPONSE);
 }
 
+// crbug/1444165: Do not use StringPrintf with StringPiece, c-strings are
+// expected.
+TEST_F(KidsExternalFetcherTest, CreatesToken) {
+  AccountInfo account = identity_test_env_.MakePrimaryAccountAvailable(
+      "bob@gmail.com", ConsentLevel::kSignin);
+  Receiver<ListFamilyMembersRequest, ListFamilyMembersResponse> receiver;
+
+  auto fetcher = FetchListFamilyMembers(
+      *identity_test_env_.identity_manager(),
+      test_url_loader_factory_.GetSafeWeakWrapper(),
+      BindOnce(&Receiver<ListFamilyMembersRequest,
+                         ListFamilyMembersResponse>::Receive,
+               base::Unretained(&receiver)),
+      test_fetcher_config_);
+
+  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
+      "token", Time::Max());
+
+  // That's enough: request is pending, so token is accepted.
+  TestURLLoaderFactory::PendingRequest* pending_request =
+      test_url_loader_factory_.GetPendingRequest(0);
+  ASSERT_NE(nullptr, pending_request);
+
+  // Only check header format here.
+  std::string authorization_header;
+  ASSERT_TRUE(pending_request->request.headers.GetHeader(
+      net::HttpRequestHeaders::kAuthorization, &authorization_header));
+  EXPECT_EQ(authorization_header, "Bearer token");
+}
+
 TEST_F(KidsExternalFetcherTest, HandlesServerError) {
   AccountInfo account = identity_test_env_.MakePrimaryAccountAvailable(
       "bob@gmail.com", ConsentLevel::kSignin);
diff --git a/components/supervised_user/core/common/supervised_user_constants.cc b/components/supervised_user/core/common/supervised_user_constants.cc
index a930af72..911fe6e 100644
--- a/components/supervised_user/core/common/supervised_user_constants.cc
+++ b/components/supervised_user/core/common/supervised_user_constants.cc
@@ -20,7 +20,7 @@
 
 }  // namespace
 
-const char kAuthorizationHeaderFormat[] = "Bearer %s";
+const char kAuthorizationHeader[] = "Bearer";
 const char kCameraMicDisabled[] = "CameraMicDisabled";
 const char kContentPackDefaultFilteringBehavior[] =
     "ContentPackDefaultFilteringBehavior";
diff --git a/components/supervised_user/core/common/supervised_user_constants.h b/components/supervised_user/core/common/supervised_user_constants.h
index 97f11cb..ecb23ee 100644
--- a/components/supervised_user/core/common/supervised_user_constants.h
+++ b/components/supervised_user/core/common/supervised_user_constants.h
@@ -12,7 +12,7 @@
 
 // Keys for supervised user settings. These are configured remotely and mapped
 // to preferences by the SupervisedUserPrefStore.
-extern const char kAuthorizationHeaderFormat[];
+extern const char kAuthorizationHeader[];
 extern const char kCameraMicDisabled[];
 extern const char kContentPackDefaultFilteringBehavior[];
 extern const char kContentPackManualBehaviorHosts[];
diff --git a/components/sync/driver/startup_controller.cc b/components/sync/driver/startup_controller.cc
index 7bd84e6..f7741c8 100644
--- a/components/sync/driver/startup_controller.cc
+++ b/components/sync/driver/startup_controller.cc
@@ -48,11 +48,9 @@
 }  // namespace
 
 StartupController::StartupController(
-    base::RepeatingCallback<ModelTypeSet()> get_preferred_data_types,
     base::RepeatingCallback<bool()> should_start,
     base::OnceClosure start_engine)
-    : get_preferred_data_types_callback_(std::move(get_preferred_data_types)),
-      should_start_callback_(std::move(should_start)),
+    : should_start_callback_(std::move(should_start)),
       start_engine_callback_(std::move(start_engine)) {}
 
 StartupController::~StartupController() = default;
@@ -81,8 +79,7 @@
   // - a datatype has requested an immediate start of sync, or
   // - sync needs to start up the engine immediately to provide control state
   //   and encryption information to the UI.
-  if (!force_immediate && !bypass_deferred_startup_ &&
-      get_preferred_data_types_callback_.Run().Has(SESSIONS)) {
+  if (!force_immediate && !bypass_deferred_startup_) {
     if (first_start) {
       base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE,
diff --git a/components/sync/driver/startup_controller.h b/components/sync/driver/startup_controller.h
index 9f2b7cb3..40805b56 100644
--- a/components/sync/driver/startup_controller.h
+++ b/components/sync/driver/startup_controller.h
@@ -28,10 +28,8 @@
     STARTED
   };
 
-  StartupController(
-      base::RepeatingCallback<ModelTypeSet()> get_preferred_data_types,
-      base::RepeatingCallback<bool()> should_start,
-      base::OnceClosure start_engine);
+  StartupController(base::RepeatingCallback<bool()> should_start,
+                    base::OnceClosure start_engine);
   ~StartupController();
 
   // Starts up sync if it is requested by the user and preconditions are met.
@@ -75,9 +73,6 @@
   // Records time spent in deferred state with UMA histograms.
   void RecordTimeDeferred(DeferredInitTrigger trigger);
 
-  const base::RepeatingCallback<ModelTypeSet()>
-      get_preferred_data_types_callback_;
-
   // A function that can be invoked repeatedly to determine whether sync should
   // be started. |start_engine_| should not be invoked unless this returns true.
   const base::RepeatingCallback<bool()> should_start_callback_;
diff --git a/components/sync/driver/startup_controller_unittest.cc b/components/sync/driver/startup_controller_unittest.cc
index dd26fc4..ff4d06be 100644
--- a/components/sync/driver/startup_controller_unittest.cc
+++ b/components/sync/driver/startup_controller_unittest.cc
@@ -19,18 +19,12 @@
 
   void SetUp() override {
     controller_ = std::make_unique<StartupController>(
-        base::BindRepeating(&StartupControllerTest::GetPreferredDataTypes,
-                            base::Unretained(this)),
         base::BindRepeating(&StartupControllerTest::ShouldStart,
                             base::Unretained(this)),
         base::BindRepeating(&StartupControllerTest::FakeStartBackend,
                             base::Unretained(this)));
   }
 
-  void SetPreferredDataTypes(const ModelTypeSet& types) {
-    preferred_types_ = types;
-  }
-
   void SetShouldStart(bool should_start) { should_start_ = should_start; }
 
   void ExpectStarted() {
@@ -59,13 +53,11 @@
   void RunDeferredTasks() { task_environment_.FastForwardUntilNoTasksRemain(); }
 
  private:
-  ModelTypeSet GetPreferredDataTypes() { return preferred_types_; }
   bool ShouldStart() { return should_start_; }
   void FakeStartBackend() { started_ = true; }
 
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  ModelTypeSet preferred_types_ = UserTypes();
   bool should_start_ = false;
   bool started_ = false;
   std::unique_ptr<StartupController> controller_;
@@ -122,21 +114,6 @@
   ExpectStarted();
 }
 
-// Test that we start immediately if sessions is disabled.
-TEST_F(StartupControllerTest, NoDeferralWithoutSessionsSync) {
-  ModelTypeSet types(UserTypes());
-  // Disabling sessions means disabling 4 types due to groupings.
-  types.Remove(SESSIONS);
-  types.Remove(PROXY_TABS);
-  types.Remove(TYPED_URLS);
-  types.Remove(SUPERVISED_USER_SETTINGS);
-  SetPreferredDataTypes(types);
-
-  SetShouldStart(true);
-  controller()->TryStart(/*force_immediate=*/false);
-  ExpectStarted();
-}
-
 // Sanity check that the fallback timer doesn't fire before startup
 // conditions are met.
 TEST_F(StartupControllerTest, FallbackTimerWaits) {
diff --git a/components/sync/driver/sync_service_impl.cc b/components/sync/driver/sync_service_impl.cc
index 6664b238..2e102dd 100644
--- a/components/sync/driver/sync_service_impl.cc
+++ b/components/sync/driver/sync_service_impl.cc
@@ -208,8 +208,6 @@
   DCHECK(IsSyncAllowedByFlag());
 
   startup_controller_ = std::make_unique<StartupController>(
-      base::BindRepeating(&SyncServiceImpl::GetPreferredDataTypes,
-                          base::Unretained(this)),
       base::BindRepeating(&SyncServiceImpl::IsEngineAllowedToRun,
                           base::Unretained(this)),
       base::BindOnce(&SyncServiceImpl::StartUpSlowEngineComponents,
@@ -288,6 +286,24 @@
                          user_settings_->IsFirstSetupComplete(),
                          is_regular_profile_for_uma_);
 
+  if (base::FeatureList::IsEnabled(
+          kSyncAllowClearingMetadataWhenDataTypeIsStopped) &&
+      // Selected types may soon start depending on the signin state. This check
+      // should help avoid accidentally clearing stuff.
+      // For localsync, it can be assumed that all info is fully loaded.
+      (IsLocalSyncEnabled() ||
+       auth_manager_->IsActiveAccountInfoFullyLoaded())) {
+    // Call Stop() on controllers for non-preferred types to clear metadata.
+    // This allows clearing metadata for types disabled in previous run early-on
+    // during initialization.
+    ModelTypeSet preferred_types = GetDataTypesToConfigure();
+    for (auto& [type, controller] : data_type_controllers_) {
+      if (!preferred_types.Has(type)) {
+        controller->Stop(CLEAR_METADATA, base::DoNothing());
+      }
+    }
+  }
+
   // Auto-start means the first time the profile starts up, sync should start up
   // immediately. Since IsSyncRequested() is false by default and nobody else
   // will set it, we need to set it here.
@@ -601,8 +617,6 @@
   sync_enabled_weak_factory_.InvalidateWeakPtrs();
 
   startup_controller_ = std::make_unique<StartupController>(
-      base::BindRepeating(&SyncServiceImpl::GetPreferredDataTypes,
-                          base::Unretained(this)),
       base::BindRepeating(&SyncServiceImpl::IsEngineAllowedToRun,
                           base::Unretained(this)),
       base::BindOnce(&SyncServiceImpl::StartUpSlowEngineComponents,
diff --git a/components/sync/driver/sync_service_impl_startup_unittest.cc b/components/sync/driver/sync_service_impl_startup_unittest.cc
index 0fab60b..4d58a9aa 100644
--- a/components/sync/driver/sync_service_impl_startup_unittest.cc
+++ b/components/sync/driver/sync_service_impl_startup_unittest.cc
@@ -241,7 +241,7 @@
 
   CreateSyncService(SyncServiceImpl::MANUAL_START);
   sync_service()->Initialize();
-  base::RunLoop().RunUntilIdle();
+  FastForwardUntilNoTasksRemain();
 
   // SyncServiceImpl should now be active, but of course not have an access
   // token.
@@ -275,9 +275,7 @@
   SimulateTestUserSigninAndEnableSyncFeature();
   sync_prefs()->SetFirstSetupComplete();
 
-  // Note: Deferred startup is only enabled if SESSIONS is among the preferred
-  // data types.
-  CreateSyncService(SyncServiceImpl::MANUAL_START, {TYPED_URLS, SESSIONS});
+  CreateSyncService(SyncServiceImpl::MANUAL_START);
   sync_service()->Initialize();
 
   ASSERT_EQ(SyncService::TransportState::START_DEFERRED,
@@ -358,7 +356,7 @@
   // Prevent automatic (and successful) completion of engine initialization.
   component_factory()->AllowFakeEngineInitCompletion(false);
   sync_service()->Initialize();
-  base::RunLoop().RunUntilIdle();
+  FastForwardUntilNoTasksRemain();
   // Simulate an auth error while downloading control types.
   engine()->TriggerInitializationCompletion(/*success=*/false);
 
@@ -426,7 +424,7 @@
   // configure the DataTypeManager. In this test, all of these operations are
   // synchronous.
   sync_service()->Initialize();
-  base::RunLoop().RunUntilIdle();
+  FastForwardUntilNoTasksRemain();
   EXPECT_NE(nullptr, data_type_manager());
   EXPECT_EQ(DataTypeManager::CONFIGURED, data_type_manager()->state());
   EXPECT_EQ(SyncService::TransportState::ACTIVE,
@@ -440,7 +438,7 @@
   CreateSyncService(SyncServiceImpl::MANUAL_START);
 
   sync_service()->Initialize();
-  base::RunLoop().RunUntilIdle();
+  FastForwardUntilNoTasksRemain();
   ASSERT_TRUE(sync_service()->IsSyncFeatureActive());
   ASSERT_EQ(DataTypeManager::CONFIGURED, data_type_manager()->state());
   ASSERT_EQ(SyncService::TransportState::ACTIVE,
@@ -553,10 +551,9 @@
   SimulateTestUserSigninAndEnableSyncFeature();
   CreateSyncService(SyncServiceImpl::MANUAL_START);
 
-  // Initialize() should be enough to kick off Sync startup (which is instant in
-  // this test).
+  // Initialize() and wait for deferred startup.
   sync_service()->Initialize();
-  base::RunLoop().RunUntilIdle();
+  FastForwardUntilNoTasksRemain();
   EXPECT_TRUE(sync_service()->IsEngineInitialized());
   EXPECT_EQ(SyncService::DisableReasonSet(),
             sync_service()->GetDisableReasons());
@@ -631,7 +628,7 @@
   // Prevent automatic (and successful) completion of engine initialization.
   component_factory()->AllowFakeEngineInitCompletion(false);
   sync_service()->Initialize();
-  base::RunLoop().RunUntilIdle();
+  FastForwardUntilNoTasksRemain();
 
   // Simulate a failure while downloading control types.
   engine()->TriggerInitializationCompletion(/*success=*/false);
@@ -652,10 +649,7 @@
   // We've never completed startup.
   ASSERT_FALSE(sync_prefs()->IsFirstSetupComplete());
 
-  // Note: Deferred startup is only enabled if SESSIONS is among the preferred
-  // data types.
-  CreateSyncService(SyncServiceImpl::MANUAL_START,
-                    ModelTypeSet(SESSIONS, TYPED_URLS));
+  CreateSyncService(SyncServiceImpl::MANUAL_START, ModelTypeSet(SESSIONS));
   sync_service()->Initialize();
   ASSERT_FALSE(sync_service()->CanSyncFeatureStart());
 
@@ -740,10 +734,7 @@
   sync_prefs()->SetFirstSetupComplete();
   sync_prefs()->SetSyncRequested(true);
 
-  // Note: Deferred startup is only enabled if SESSIONS is among the preferred
-  // data types.
-  CreateSyncService(SyncServiceImpl::MANUAL_START,
-                    ModelTypeSet(SESSIONS, TYPED_URLS));
+  CreateSyncService(SyncServiceImpl::MANUAL_START, ModelTypeSet(SESSIONS));
   sync_service()->Initialize();
   ASSERT_TRUE(sync_service()->CanSyncFeatureStart());
 
@@ -784,4 +775,29 @@
             sync_service()->GetTransportState());
 }
 
+TEST_F(SyncServiceImplStartupTest,
+       ShouldClearMetadataForAlreadyDisabledTypesBeforeConfigurationDone) {
+  sync_prefs()->SetFirstSetupComplete();
+  // Simulate types disabled during previous run.
+  sync_prefs()->SetSelectedTypes(
+      /*keep_everything_synced=*/false,
+      /*registered_types=*/
+      {UserSelectableType::kBookmarks, UserSelectableType::kReadingList},
+      /*selected_types=*/{UserSelectableType::kBookmarks});
+  sync_prefs()->SetSyncRequested(true);
+
+  SimulateTestUserSigninAndEnableSyncFeature();
+
+  CreateSyncService(SyncServiceImpl::MANUAL_START,
+                    /*registered_types=*/ModelTypeSet(BOOKMARKS, READING_LIST));
+
+  sync_service()->Initialize();
+
+  // Metadata was cleared for disabled types ...
+  EXPECT_EQ(1,
+            get_controller(READING_LIST)->model()->clear_metadata_call_count());
+  // ... but not for the ones not disabled.
+  EXPECT_EQ(0, get_controller(BOOKMARKS)->model()->clear_metadata_call_count());
+}
+
 }  // namespace syncer
diff --git a/components/sync_preferences/pref_service_syncable_unittest.cc b/components/sync_preferences/pref_service_syncable_unittest.cc
index c35d760..dd6eff4 100644
--- a/components/sync_preferences/pref_service_syncable_unittest.cc
+++ b/components/sync_preferences/pref_service_syncable_unittest.cc
@@ -113,11 +113,9 @@
 }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-constexpr ModelTypeSet kAllPreferenceModelTypes(
-    syncer::PREFERENCES,
-    syncer::PRIORITY_PREFERENCES,
-    syncer::OS_PREFERENCES,
-    syncer::OS_PRIORITY_PREFERENCES);
+constexpr ModelTypeSet kAllPreferenceModelTypes = {
+    syncer::PREFERENCES, syncer::PRIORITY_PREFERENCES, syncer::OS_PREFERENCES,
+    syncer::OS_PRIORITY_PREFERENCES};
 
 MATCHER_P(MatchesModelType, model_type, "") {
   const syncer::SyncChange& sync_change = arg;
@@ -1051,13 +1049,13 @@
 TEST_F(PrefServiceSyncableChromeOsTest, IsPrefRegistered) {
   CreatePrefService();
   EXPECT_TRUE(GetRegisteredModelTypes(kUnsyncedPreferenceName).Empty());
-  EXPECT_EQ(ModelTypeSet(syncer::PREFERENCES),
+  EXPECT_EQ(ModelTypeSet({syncer::PREFERENCES}),
             GetRegisteredModelTypes(kBrowserPrefName));
-  EXPECT_EQ(ModelTypeSet(syncer::PRIORITY_PREFERENCES),
+  EXPECT_EQ(ModelTypeSet({syncer::PRIORITY_PREFERENCES}),
             GetRegisteredModelTypes(kBrowserPriorityPrefName));
-  EXPECT_EQ(ModelTypeSet(syncer::OS_PREFERENCES),
+  EXPECT_EQ(ModelTypeSet({syncer::OS_PREFERENCES}),
             GetRegisteredModelTypes(kOsPrefName));
-  EXPECT_EQ(ModelTypeSet(syncer::OS_PRIORITY_PREFERENCES),
+  EXPECT_EQ(ModelTypeSet({syncer::OS_PRIORITY_PREFERENCES}),
             GetRegisteredModelTypes(kOsPriorityPrefName));
 
   // The associator for PREFERENCES knows about OS prefs so that local updates
diff --git a/components/translate/ios/browser/ios_translate_driver.h b/components/translate/ios/browser/ios_translate_driver.h
index 6369cc8..6afc1dce 100644
--- a/components/translate/ios/browser/ios_translate_driver.h
+++ b/components/translate/ios/browser/ios_translate_driver.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "components/language/ios/browser/ios_language_detection_tab_helper.h"
diff --git a/components/ukm/app_source_url_recorder.h b/components/ukm/app_source_url_recorder.h
index 6e70008..cc1a26c 100644
--- a/components/ukm/app_source_url_recorder.h
+++ b/components/ukm/app_source_url_recorder.h
@@ -29,6 +29,11 @@
 namespace badging {
 class BadgeManager;
 }  // namespace badging
+
+namespace web_app {
+class DesktopWebAppUkmRecorder;
+}  // namespace web_app
+
 namespace ukm {
 
 BASE_FEATURE(kUkmAppLogging, "UkmAppLogging", base::FEATURE_ENABLED_BY_DEFAULT);
@@ -45,6 +50,8 @@
 
   friend class badging::BadgeManager;
 
+  friend class web_app::DesktopWebAppUkmRecorder;
+
   // Get a UKM SourceId with the prefix "app://" for a Chrome app with `app_id`,
   // a unique hash string to identify the app. For example,
   // "mgndgikekgjfcpckkfioiadnlibdjbkf" is the `app_id` for Chrome browser, and
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
index b871245b..d1fff74c 100644
--- a/components/ukm/ukm_recorder_impl.cc
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -64,7 +64,7 @@
       return true;
     }
     case ukm::SourceIdObj::Type::DEFAULT:
-    case ukm::SourceIdObj::Type::DESKTOP_WEB_APP_ID:
+    case ukm::SourceIdObj::Type::DEPRECATED_DESKTOP_WEB_APP_ID:
     case ukm::SourceIdObj::Type::WORKER_ID:
       return false;
   }
@@ -833,7 +833,7 @@
     case SourceIdType::HISTORY_ID:
     case SourceIdType::WEBAPK_ID:
     case SourceIdType::PAYMENT_APP_ID:
-    case SourceIdType::DESKTOP_WEB_APP_ID:
+    case SourceIdType::DEPRECATED_DESKTOP_WEB_APP_ID:
     case SourceIdType::WORKER_ID:
     case SourceIdType::NO_URL_ID:
     case SourceIdType::REDIRECT_ID:
@@ -899,7 +899,7 @@
     }
     case ukm::SourceIdObj::Type::DEFAULT:
     case ukm::SourceIdObj::Type::APP_ID:
-    case ukm::SourceIdObj::Type::DESKTOP_WEB_APP_ID:
+    case ukm::SourceIdObj::Type::DEPRECATED_DESKTOP_WEB_APP_ID:
     case ukm::SourceIdObj::Type::NAVIGATION_ID:
     case ukm::SourceIdObj::Type::WORKER_ID:
     case ukm::SourceIdObj::Type::REDIRECT_ID:
diff --git a/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc b/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc
index d8a4eed..409ec12 100644
--- a/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc
+++ b/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc
@@ -105,8 +105,7 @@
       syncer::SyncService::TransportState::INITIALIZING);
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
-      /*types=*/syncer::UserSelectableTypeSet(
-          syncer::UserSelectableType::kHistory));
+      /*types=*/{syncer::UserSelectableType::kHistory});
 
   sync_service_.FireOnStateChangeOnAllObservers();
   EXPECT_EQ(helper->GetConsentState(),
@@ -145,8 +144,7 @@
 
     sync_service_.GetUserSettings()->SetSelectedTypes(
         /*sync_everything=*/false,
-        /*types=*/syncer::UserSelectableTypeSet(
-            syncer::UserSelectableType::kBookmarks));
+        /*types=*/{syncer::UserSelectableType::kBookmarks});
 
     sync_service_.FireOnStateChangeOnAllObservers();
     EXPECT_TRUE(helper->IsEnabled());
diff --git a/components/user_education/views/help_bubble_view.h b/components/user_education/views/help_bubble_view.h
index 63638914..9b0edb1 100644
--- a/components/user_education/views/help_bubble_view.h
+++ b/components/user_education/views/help_bubble_view.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
diff --git a/components/user_notes/storage/user_note_database.h b/components/user_notes/storage/user_note_database.h
index b48c7d39..4449c28 100644
--- a/components/user_notes/storage/user_note_database.h
+++ b/components/user_notes/storage/user_note_database.h
@@ -9,6 +9,7 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
diff --git a/components/viz/common/transition_utils.cc b/components/viz/common/transition_utils.cc
index 7737cc53..3d46bf86 100644
--- a/components/viz/common/transition_utils.cc
+++ b/components/viz/common/transition_utils.cc
@@ -214,40 +214,6 @@
 }
 
 // static
-float TransitionUtils::ComputeAccumulatedOpacity(
-    const CompositorRenderPassList& render_passes,
-    CompositorRenderPassId target_id) {
-  float opacity = 1.f;
-  bool found_render_pass = false;
-  for (auto& render_pass : render_passes) {
-    // If we haven't even reached the needed render pass, then we don't need to
-    // iterate the quads. Note that we also don't iterate the quads of the
-    // target render pass itself, since it can't draw itself.
-    if (!found_render_pass) {
-      found_render_pass = render_pass->id == target_id;
-      continue;
-    }
-
-    for (auto* quad : render_pass->quad_list) {
-      if (quad->material != DrawQuad::Material::kCompositorRenderPass)
-        continue;
-
-      const auto* pass_quad = CompositorRenderPassDrawQuad::MaterialCast(quad);
-      if (pass_quad->render_pass_id != target_id)
-        continue;
-
-      // TODO(vmpstr): We need to consider different blend modes as well,
-      // although it's difficult in general. For the simple case of common
-      // SrcOver blend modes however, we can just multiply the opacity.
-      opacity *= pass_quad->shared_quad_state->opacity;
-      target_id = render_pass->id;
-      break;
-    }
-  }
-  return opacity;
-}
-
-// static
 std::unique_ptr<CompositorRenderPass>
 TransitionUtils::CopyPassWithQuadFiltering(
     const CompositorRenderPass& source_pass,
diff --git a/components/viz/common/transition_utils.h b/components/viz/common/transition_utils.h
index 364f97d..120eb93 100644
--- a/components/viz/common/transition_utils.h
+++ b/components/viz/common/transition_utils.h
@@ -17,15 +17,6 @@
 // This class is a collection of utils used by view transition API.
 class VIZ_COMMON_EXPORT TransitionUtils {
  public:
-  // Computes the opacity value of the given target_id as drawn in the root
-  // render pass. It looks through the chain of CompositorRenderPassDrawQuads to
-  // accumulate this value. Note that it is assumed, and not checked, that the
-  // render pass draw quads use "regular" opacity accumulation (i.e. kSrcOver
-  // blend mode).
-  static float ComputeAccumulatedOpacity(
-      const CompositorRenderPassList& render_passes,
-      CompositorRenderPassId target_id);
-
   // Creates a deep copy of |source_pass| retaining all state. |filter_callback|
   // is invoked for each render pass draw quad to let the caller modify the copy
   // of these quads. If the callback returns true the quad is skipped otherwise
diff --git a/components/viz/common/viz_utils.cc b/components/viz/common/viz_utils.cc
index 6852572..5ff2c417 100644
--- a/components/viz/common/viz_utils.cc
+++ b/components/viz/common/viz_utils.cc
@@ -167,10 +167,7 @@
     const DrawQuad& rpdq,
     const cc::FilterOperations& filters) {
   const SharedQuadState* shared_quad_state = rpdq.shared_quad_state;
-  float max_pixel_movement = filters.MaximumPixelMovement();
-  gfx::RectF rect(rpdq.rect);
-  rect.Inset(-max_pixel_movement);
-  gfx::Rect expanded_rect = gfx::ToEnclosingRect(rect);
+  gfx::Rect expanded_rect = filters.ExpandRectForPixelMovement(rpdq.rect);
 
   // expanded_rect in the target space
   return cc::MathUtil::MapEnclosingClippedRect(
diff --git a/components/viz/service/display/dc_layer_overlay.cc b/components/viz/service/display/dc_layer_overlay.cc
index bd0704b2..bc44bb7 100644
--- a/components/viz/service/display/dc_layer_overlay.cc
+++ b/components/viz/service/display/dc_layer_overlay.cc
@@ -63,18 +63,6 @@
   kMaxValue = DC_LAYER_FAILED_YUV_VIDEO_QUAD_HLG,
 };
 
-gfx::RectF GetExpandedRectWithPixelMovingFilter(
-    const AggregatedRenderPassDrawQuad* rpdq,
-    float max_pixel_movement) {
-  const SharedQuadState* shared_quad_state = rpdq->shared_quad_state;
-  gfx::RectF expanded_rect(rpdq->rect);
-  expanded_rect.Inset(-max_pixel_movement);
-
-  // expanded_rect in the target space
-  return cc::MathUtil::MapClippedRect(
-      shared_quad_state->quad_to_target_transform, expanded_rect);
-}
-
 DCLayerResult ValidateYUVOverlay(
     const gfx::ProtectedVideoType& protected_video_type,
     const gfx::ColorSpace& video_color_space,
@@ -350,9 +338,8 @@
       auto render_pass_it = render_pass_filters.find(rpdq->render_pass_id);
       if (render_pass_it != render_pass_filters.end()) {
         auto* filters = render_pass_it->second;
-        float max_pixel_movement = filters->MaximumPixelMovement();
-        overlap_rect =
-            GetExpandedRectWithPixelMovingFilter(rpdq, max_pixel_movement);
+        overlap_rect = gfx::RectF(
+            GetExpandedRectWithPixelMovingForegroundFilter(*rpdq, *filters));
         has_pixel_moving_filter = true;
       }
     }
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index 1c5d0cbb..fabbc0b 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -487,9 +487,7 @@
     // visible bounds.
     auto filter_it = render_pass_filters_.find(rpdq->render_pass_id);
     if (filter_it != render_pass_filters_.end()) {
-      gfx::RectF rect(target_rect);
-      rect.Outset(filter_it->second->MaximumPixelMovement());
-      target_rect = gfx::ToEnclosingRect(rect);
+      target_rect = filter_it->second->ExpandRectForPixelMovement(target_rect);
     }
   }
 
diff --git a/components/viz/service/display/overlay_dc_unittest.cc b/components/viz/service/display/overlay_dc_unittest.cc
index 70c28373..396992f 100644
--- a/components/viz/service/display/overlay_dc_unittest.cc
+++ b/components/viz/service/display/overlay_dc_unittest.cc
@@ -1073,8 +1073,8 @@
   SharedQuadState* shared_quad_state_rpdq = pass->shared_quad_state_list.back();
   // The pixel-moving render pass draw quad itself (rpdq->rect) doesn't
   // intersect with kOverlayRect(0, 0, 256, 256), but the expanded draw quad
-  // (rpdq->rect(260, 260, 100, 100) + MaximumPixelMovement (2 * 10.f) = (240,
-  // 240, 140, 140)) does.
+  // (rpdq->rect(260, 260, 100, 100) + blur filter pixel movement (2 * 10.f) =
+  // (240, 240, 140, 140)) does.
 
   CreateRenderPassDrawQuadAt(pass.get(), shared_quad_state_rpdq, filter_rect,
                              filter_render_pass_id);
@@ -1110,7 +1110,7 @@
 
   EXPECT_EQ(1U, dc_layer_list.size());
   // Make sure the video is in an underlay mode if the overlay quad intersects
-  // with (rpdq->rect + MaximumPixelMovement()).
+  // with expanded rpdq->rect.
   EXPECT_EQ(-1, dc_layer_list.back().plane_z_order);
   EXPECT_EQ(gfx::Rect(0, 0, 360, 360), damage_rect_);
 }
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index a78ee63..807eff5 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -1688,11 +1688,10 @@
       // offset backdrop filters may be involved.
 
       // For the pixel-moving foreground filters, all effects can be expanded
-      // outside the RenderPassDrawQuad rect to the size of rect +
-      // filters.MaximumPixelMovement(). Therefore, we have to check if
-      // (rpdq->rect + MaximumPixelMovement()) intersects the damage under it.
-      // Then we extend the damage rect to include the (rpdq->rect +
-      // MaximumPixelMovement()).
+      // outside the RenderPassDrawQuad rect based on filter pixel movement.
+      // Therefore, we have to check if the expanded rpdq->rect intersects the
+      // damage under it. Then we extend the damage rect to include the expanded
+      // rpdq->rect.
 
       // Expand the damage to cover entire |output_rect| if the |render_pass|
       // has pixel-moving foreground filter.
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc
index 9ca9a5e..2ce5458f 100644
--- a/components/viz/service/display/surface_aggregator_unittest.cc
+++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -5370,10 +5370,9 @@
     filter_pass->filters.Append(cc::FilterOperation::CreateZoomFilter(2, 4));
     auto* root_pass = root_pass_list[2].get();
     // Set the root damage rect which doesn't intersect with the expanded
-    // filter_pass quad (-4, -4, 13, 13) (filter quad (0, 0, 5, 5) +
-    // MaximumPixelMovement(2 * 3 = 6)), so we don't have to add more damage
-    // from the filter_pass and the first render pass draw quad will not be
-    // drawn.
+    // filter_pass quad (-4, -4, 13, 13) (filter quad (0, 0, 5, 5) + blur filter
+    // pixel movement (2 * 3 = 6)), so we don't have to add more damage from the
+    // filter_pass and the first render pass draw quad will not be drawn.
     root_pass->damage_rect = gfx::Rect(20, 20, 2, 2);
     SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
                           &root_pass_list, std::move(referenced_surfaces),
@@ -5432,8 +5431,8 @@
         gfx::Point(0, 0), 10, SkColors::kTransparent));
     filter_pass->filters.Append(cc::FilterOperation::CreateZoomFilter(2, 20));
     auto* root_pass = root_pass_list[2].get();
-    // Make the root damage rect intersect with the expanded filter_pass
-    // quad (filter quad (0, 0, 5, 5) + MaximumPixelMovement(10 * 3) = (-30,
+    // Make the root damage rect intersect with the expanded filter_pass quad
+    // (filter quad (0, 0, 5, 5) + blur filter pixel movement (10 * 3) = (-30,
     // -30, 65, 65)), but not with filter_pass quad itself (0, 0, 5, 5). The
     // first render pass will be drawn.
     root_pass->damage_rect = gfx::Rect(20, 20, 2, 2);
@@ -5452,9 +5451,9 @@
     EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[2]->damage_rect);
     // The filter pass intersects with the root surface damage, the root damage
     // should increase.
-    // damage_rect = original root damage (0, 0, 5, 5) + MaximumPixelMovement(10
-    // * 3) = (-30, -30, 65, 65). Then intersects with the root output_rect (0,
-    // 0, 100, 100) = (0, 0, 35, 35).
+    // damage_rect = original root damage (0, 0, 5, 5) + blur filter pixel
+    // movement (10 * 3) = (-30, -30, 65, 65). Then intersects with the root
+    // output_rect (0, 0, 100, 100) = (0, 0, 35, 35).
     EXPECT_EQ(gfx::Rect(0, 0, 35, 35), aggregated_pass_list[3]->damage_rect);
     EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
     EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
diff --git a/components/viz/service/surfaces/surface_saved_frame.cc b/components/viz/service/surfaces/surface_saved_frame.cc
index 8a21435..dbff531 100644
--- a/components/viz/service/surfaces/surface_saved_frame.cc
+++ b/components/viz/service/surfaces/surface_saved_frame.cc
@@ -110,14 +110,12 @@
   if (shared_pass_index >= directive_.shared_elements().size())
     return nullptr;
 
-  RenderPassDrawData draw_data(
-      render_pass, TransitionUtils::ComputeAccumulatedOpacity(render_pass_list,
-                                                              render_pass.id));
+  RenderPassDrawData draw_data(render_pass);
   auto request = std::make_unique<CopyOutputRequest>(
       kResultFormat, kResultDestination,
       base::BindOnce(&SurfaceSavedFrame::NotifyCopyOfOutputComplete,
-                     weak_factory_.GetMutableWeakPtr(), ResultType::kShared,
-                     shared_pass_index, draw_data));
+                     weak_factory_.GetMutableWeakPtr(), shared_pass_index,
+                     draw_data));
   request->set_result_task_runner(
       base::SingleThreadTaskRunner::GetCurrentDefault());
   return request;
@@ -138,7 +136,6 @@
 }
 
 void SurfaceSavedFrame::NotifyCopyOfOutputComplete(
-    ResultType type,
     size_t shared_index,
     const RenderPassDrawData& data,
     std::unique_ptr<CopyOutputResult> output_copy) {
@@ -163,15 +160,10 @@
     frame_result_->shared_results.resize(directive_.shared_elements().size());
   }
 
-  OutputCopyResult* slot = nullptr;
-  if (type == ResultType::kRoot) {
-    slot = &frame_result_->root_result;
-  } else {
-    DCHECK(type == ResultType::kShared);
-    CHECK_LT(shared_index, frame_result_->shared_results.size());
-    DCHECK(!frame_result_->shared_results[shared_index]);
-    slot = &frame_result_->shared_results[shared_index].emplace();
-  }
+  CHECK_LT(shared_index, frame_result_->shared_results.size());
+  DCHECK(!frame_result_->shared_results[shared_index]);
+  OutputCopyResult* slot =
+      &frame_result_->shared_results[shared_index].emplace();
 
   DCHECK(slot);
   DCHECK_EQ(output_copy->size(), data.size);
@@ -208,13 +200,14 @@
                                  kDefaultTextureSizeForTesting.height()));
 
   InitFrameResult();
-  frame_result_->root_result.bitmap = std::move(bitmap);
-  frame_result_->root_result.draw_data.size = kDefaultTextureSizeForTesting;
-  frame_result_->root_result.draw_data.target_transform.MakeIdentity();
-  frame_result_->root_result.draw_data.opacity = 1.f;
-  frame_result_->root_result.is_software = true;
-
   frame_result_->shared_results.resize(directive_.shared_elements().size());
+  for (auto& result : frame_result_->shared_results) {
+    result.emplace();
+    result->bitmap = std::move(bitmap);
+    result->draw_data.size = kDefaultTextureSizeForTesting;
+    result->is_software = true;
+  }
+
   copy_request_count_ = 0;
   valid_result_count_ = ExpectedResultCount();
   weak_factory_.InvalidateWeakPtrs();
@@ -228,17 +221,8 @@
 
 SurfaceSavedFrame::RenderPassDrawData::RenderPassDrawData() = default;
 SurfaceSavedFrame::RenderPassDrawData::RenderPassDrawData(
-    const CompositorRenderPass& render_pass,
-    float opacity)
-    : opacity(opacity) {
-  // During copy request, the origin for |render_pass|'s output_rect is mapped
-  // to the origin of the texture in the result. We account for that here.
-  size = render_pass.output_rect.size();
-
-  target_transform = render_pass.transform_to_root_target;
-  target_transform.Translate(render_pass.output_rect.x(),
-                             render_pass.output_rect.y());
-}
+    const CompositorRenderPass& render_pass)
+    : size(render_pass.output_rect.size()) {}
 
 SurfaceSavedFrame::OutputCopyResult::OutputCopyResult() = default;
 SurfaceSavedFrame::OutputCopyResult::OutputCopyResult(
diff --git a/components/viz/service/surfaces/surface_saved_frame.h b/components/viz/service/surfaces/surface_saved_frame.h
index 90a9270..d33eafa 100644
--- a/components/viz/service/surfaces/surface_saved_frame.h
+++ b/components/viz/service/surfaces/surface_saved_frame.h
@@ -29,16 +29,10 @@
 
   struct RenderPassDrawData {
     RenderPassDrawData();
-    RenderPassDrawData(const CompositorRenderPass& render_pass, float opacity);
+    explicit RenderPassDrawData(const CompositorRenderPass& render_pass);
 
     // This represents the size of the copied texture.
     gfx::Size size;
-    // This is a transform that takes `rect` into a root render pass space. Note
-    // that this makes this result dependent on the structure of the compositor
-    // frame render pass list used to request the copy output.
-    gfx::Transform target_transform;
-    // Opacity accumulated from the original frame.
-    float opacity = 1.f;
   };
 
   struct OutputCopyResult {
@@ -74,7 +68,6 @@
 
     FrameResult& operator=(FrameResult&& other);
 
-    OutputCopyResult root_result;
     std::vector<absl::optional<OutputCopyResult>> shared_results;
     base::flat_set<ViewTransitionElementResourceId> empty_resource_ids;
   };
@@ -100,14 +93,11 @@
   base::flat_set<ViewTransitionElementResourceId> GetEmptyResourceIds() const;
 
  private:
-  enum class ResultType { kRoot, kShared };
-
   std::unique_ptr<CopyOutputRequest> CreateCopyRequestIfNeeded(
       const CompositorRenderPass& render_pass,
       const CompositorRenderPassList& render_pass_list) const;
 
-  void NotifyCopyOfOutputComplete(ResultType type,
-                                  size_t shared_index,
+  void NotifyCopyOfOutputComplete(size_t shared_index,
                                   const RenderPassDrawData& info,
                                   std::unique_ptr<CopyOutputResult> result);
 
diff --git a/components/viz/service/transitions/surface_animation_manager.cc b/components/viz/service/transitions/surface_animation_manager.cc
index 17723cb..d0033142 100644
--- a/components/viz/service/transitions/surface_animation_manager.cc
+++ b/components/viz/service/transitions/surface_animation_manager.cc
@@ -15,6 +15,7 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
+#include "cc/base/math_util.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
@@ -33,6 +34,7 @@
 #include "ui/gfx/animation/keyframe/timing_function.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/geometry/size_f.h"
@@ -53,7 +55,7 @@
 void ReplaceSharedElementWithRenderPass(
     CompositorRenderPass* target_render_pass,
     const SharedElementDrawQuad& shared_element_quad,
-    const CompositorRenderPass* shared_element_content_pass) {
+    CompositorRenderPass* shared_element_content_pass) {
   auto pass_id = shared_element_content_pass->id;
   const gfx::Rect& shared_pass_output_rect =
       shared_element_content_pass->output_rect;
@@ -64,7 +66,6 @@
 
   gfx::Transform transform = GetViewTransitionTransform(
       shared_element_quad.rect, shared_pass_output_rect);
-
   copied_quad_state->quad_to_target_transform.PreConcat(transform);
 
   auto* render_pass_quad =
@@ -200,7 +201,7 @@
 bool SurfaceAnimationManager::FilterSharedElementsWithRenderPassOrResource(
     std::vector<TransferableResource>* resource_list,
     const base::flat_map<ViewTransitionElementResourceId,
-                         const CompositorRenderPass*>* element_id_to_pass,
+                         CompositorRenderPass*>* element_id_to_pass,
     const DrawQuad& quad,
     CompositorRenderPass& copy_pass) {
   if (quad.material != DrawQuad::Material::kSharedElement)
@@ -276,7 +277,7 @@
   resolved_frame.metadata = active_frame.metadata.Clone();
   resolved_frame.resource_list = active_frame.resource_list;
 
-  base::flat_map<ViewTransitionElementResourceId, const CompositorRenderPass*>
+  base::flat_map<ViewTransitionElementResourceId, CompositorRenderPass*>
       element_id_to_pass;
   TransitionUtils::FilterCallback filter_callback = base::BindRepeating(
       &SurfaceAnimationManager::FilterSharedElementsWithRenderPassOrResource,
diff --git a/components/viz/service/transitions/surface_animation_manager.h b/components/viz/service/transitions/surface_animation_manager.h
index 11f01dd8..daaa49f 100644
--- a/components/viz/service/transitions/surface_animation_manager.h
+++ b/components/viz/service/transitions/surface_animation_manager.h
@@ -76,7 +76,7 @@
   bool FilterSharedElementsWithRenderPassOrResource(
       std::vector<TransferableResource>* resource_list,
       const base::flat_map<ViewTransitionElementResourceId,
-                           const CompositorRenderPass*>* element_id_to_pass,
+                           CompositorRenderPass*>* element_id_to_pass,
       const DrawQuad& quad,
       CompositorRenderPass& copy_pass);
 
diff --git a/components/viz/service/transitions/transferable_resource_tracker.cc b/components/viz/service/transitions/transferable_resource_tracker.cc
index 4d98f6aa..c4eb311a 100644
--- a/components/viz/service/transitions/transferable_resource_tracker.cc
+++ b/components/viz/service/transitions/transferable_resource_tracker.cc
@@ -42,8 +42,6 @@
   const auto& directive = saved_frame->directive();
 
   ResourceFrame resource_frame;
-  resource_frame.root = ImportResource(std::move(frame_copy->root_result));
-
   resource_frame.shared.resize(frame_copy->shared_results.size());
   for (size_t i = 0; i < frame_copy->shared_results.size(); ++i) {
     auto& shared_result = frame_copy->shared_results[i];
@@ -124,7 +122,6 @@
 }
 
 void TransferableResourceTracker::ReturnFrame(const ResourceFrame& frame) {
-  UnrefResource(frame.root.resource.id, /*count=*/1);
   for (const auto& shared : frame.shared) {
     if (shared.has_value())
       UnrefResource(shared->resource.id, /*count=*/1);
diff --git a/components/viz/service/transitions/transferable_resource_tracker.h b/components/viz/service/transitions/transferable_resource_tracker.h
index 15a41abc..bc6dfdf 100644
--- a/components/viz/service/transitions/transferable_resource_tracker.h
+++ b/components/viz/service/transitions/transferable_resource_tracker.h
@@ -43,9 +43,6 @@
 
     ResourceFrame& operator=(ResourceFrame&& other);
 
-    // The cached resource for the root content.
-    PositionedResource root;
-
     // The cached resource for each shared element. The entries here are
     // optional since copy request for an element may fail or a
     // [src_element, dst_element] has a null src_element.
diff --git a/components/viz/service/transitions/transferable_resource_tracker_unittest.cc b/components/viz/service/transitions/transferable_resource_tracker_unittest.cc
index 96d1560..3b44ddd 100644
--- a/components/viz/service/transitions/transferable_resource_tracker_unittest.cc
+++ b/components/viz/service/transitions/transferable_resource_tracker_unittest.cc
@@ -19,8 +19,9 @@
 namespace {
 
 std::unique_ptr<SurfaceSavedFrame> CreateFrameWithResult() {
+  CompositorFrameTransitionDirective::SharedElement element;
   auto directive = CompositorFrameTransitionDirective::CreateSave(
-      NavigationID::Null(), 1, {});
+      NavigationID::Null(), 1, {element});
   auto frame = std::make_unique<SurfaceSavedFrame>(
       std::move(directive),
       base::BindRepeating([](const CompositorFrameTransitionDirective&) {}));
@@ -52,23 +53,27 @@
   TransferableResourceTracker tracker(&shared_bitmap_manager_);
 
   auto frame1 = tracker.ImportResources(CreateFrameWithResult());
-  EXPECT_TRUE(HasBitmapResource(frame1.root.resource));
+  ASSERT_EQ(frame1.shared.size(), 1u);
+  const auto& resource1 = frame1.shared.at(0);
+  EXPECT_TRUE(HasBitmapResource(resource1->resource));
 
-  EXPECT_GE(frame1.root.resource.id, kVizReservedRangeStartId);
+  EXPECT_GE(resource1->resource.id, kVizReservedRangeStartId);
 
   auto frame2 = tracker.ImportResources(CreateFrameWithResult());
-  EXPECT_TRUE(HasBitmapResource(frame2.root.resource));
+  ASSERT_EQ(frame2.shared.size(), 1u);
+  const auto& resource2 = frame2.shared.at(0);
+  EXPECT_TRUE(HasBitmapResource(resource2->resource));
 
-  EXPECT_GE(frame2.root.resource.id, frame1.root.resource.id);
+  EXPECT_GE(resource2->resource.id, resource1->resource.id);
 
   tracker.ReturnFrame(frame1);
-  EXPECT_FALSE(HasBitmapResource(frame1.root.resource));
+  EXPECT_FALSE(HasBitmapResource(resource1->resource));
 
-  tracker.RefResource(frame2.root.resource.id);
+  tracker.RefResource(resource2->resource.id);
   tracker.ReturnFrame(frame2);
-  EXPECT_TRUE(HasBitmapResource(frame2.root.resource));
-  tracker.UnrefResource(frame2.root.resource.id, 1);
-  EXPECT_FALSE(HasBitmapResource(frame2.root.resource));
+  EXPECT_TRUE(HasBitmapResource(resource2->resource));
+  tracker.UnrefResource(resource2->resource.id, 1);
+  EXPECT_FALSE(HasBitmapResource(resource2->resource));
 }
 
 TEST_F(TransferableResourceTrackerTest, ExhaustedIdLoops) {
@@ -82,27 +87,31 @@
   ResourceId last_id = kInvalidResourceId;
   for (int i = 0; i < 10; ++i) {
     auto frame = tracker.ImportResources(CreateFrameWithResult());
-    EXPECT_TRUE(HasBitmapResource(frame.root.resource));
+    ASSERT_EQ(frame.shared.size(), 1u);
+    const auto& resource = frame.shared.at(0);
+    EXPECT_TRUE(HasBitmapResource(resource->resource));
 
-    EXPECT_GE(frame.root.resource.id, kVizReservedRangeStartId);
-    EXPECT_NE(frame.root.resource.id, last_id);
-    last_id = frame.root.resource.id;
+    EXPECT_GE(resource->resource.id, kVizReservedRangeStartId);
+    EXPECT_NE(resource->resource.id, last_id);
+    last_id = resource->resource.id;
     tracker.ReturnFrame(frame);
-    EXPECT_FALSE(HasBitmapResource(frame.root.resource));
+    EXPECT_FALSE(HasBitmapResource(resource->resource));
   }
 }
 
 TEST_F(TransferableResourceTrackerTest, UnrefWithCount) {
   TransferableResourceTracker tracker(&shared_bitmap_manager_);
   auto frame = tracker.ImportResources(CreateFrameWithResult());
+  ASSERT_EQ(frame.shared.size(), 1u);
+  const auto& resource = frame.shared.at(0);
   for (int i = 0; i < 1000; ++i)
-    tracker.RefResource(frame.root.resource.id);
+    tracker.RefResource(resource->resource.id);
   ASSERT_FALSE(tracker.is_empty());
-  tracker.UnrefResource(frame.root.resource.id, 1);
+  tracker.UnrefResource(resource->resource.id, 1);
   EXPECT_FALSE(tracker.is_empty());
-  tracker.UnrefResource(frame.root.resource.id, 1);
+  tracker.UnrefResource(resource->resource.id, 1);
   EXPECT_FALSE(tracker.is_empty());
-  tracker.UnrefResource(frame.root.resource.id, 999);
+  tracker.UnrefResource(resource->resource.id, 999);
   EXPECT_TRUE(tracker.is_empty());
 }
 
@@ -114,7 +123,9 @@
   TransferableResourceTracker tracker(&shared_bitmap_manager_);
 
   auto reserved = tracker.ImportResources(CreateFrameWithResult());
-  EXPECT_GE(reserved.root.resource.id, kVizReservedRangeStartId);
+  ASSERT_EQ(reserved.shared.size(), 1u);
+  const auto& resource = reserved.shared.at(0);
+  EXPECT_GE(resource->resource.id, kVizReservedRangeStartId);
 
   uint32_t next_id = std::numeric_limits<uint32_t>::max() - 3u;
   SetNextId(&tracker, next_id);
@@ -122,14 +133,16 @@
   ResourceId last_id = kInvalidResourceId;
   for (int i = 0; i < 10; ++i) {
     auto frame = tracker.ImportResources(CreateFrameWithResult());
-    EXPECT_TRUE(HasBitmapResource(frame.root.resource));
+    ASSERT_EQ(frame.shared.size(), 1u);
+    const auto& new_resource = frame.shared.at(0);
+    EXPECT_TRUE(HasBitmapResource(new_resource->resource));
 
-    EXPECT_GE(frame.root.resource.id, kVizReservedRangeStartId);
-    EXPECT_NE(frame.root.resource.id, last_id);
-    EXPECT_NE(frame.root.resource.id, reserved.root.resource.id);
-    last_id = frame.root.resource.id;
+    EXPECT_GE(new_resource->resource.id, kVizReservedRangeStartId);
+    EXPECT_NE(new_resource->resource.id, last_id);
+    EXPECT_NE(new_resource->resource.id, resource->resource.id);
+    last_id = new_resource->resource.id;
     tracker.ReturnFrame(frame);
-    EXPECT_FALSE(HasBitmapResource(frame.root.resource));
+    EXPECT_FALSE(HasBitmapResource(new_resource->resource));
   }
 }
 
diff --git a/components/webapps/browser/android/add_to_homescreen_mediator.cc b/components/webapps/browser/android/add_to_homescreen_mediator.cc
index 43fc01e..360dba7 100644
--- a/components/webapps/browser/android/add_to_homescreen_mediator.cc
+++ b/components/webapps/browser/android/add_to_homescreen_mediator.cc
@@ -80,8 +80,7 @@
   }
   // In this code path (show A2HS dialog from app banner), a maskable primary
   // icon isn't padded yet. We'll need to pad it here.
-  SetIcon(params_->primary_icon,
-          params_->has_maskable_primary_icon /*need_to_add_padding*/);
+  SetIcon(params_->primary_icon, params_->HasMaskablePrimaryIcon());
 }
 
 void AddToHomescreenMediator::StartForAppMenu(
@@ -147,7 +146,7 @@
   base::android::ScopedJavaLocalRef<jobject> java_bitmap =
       gfx::ConvertToJavaBitmap(display_icon);
   Java_AddToHomescreenMediator_setIcon(env, java_ref_, java_bitmap,
-                                       params_->has_maskable_primary_icon,
+                                       params_->HasMaskablePrimaryIcon(),
                                        need_to_add_padding);
 }
 
@@ -183,8 +182,6 @@
                           : AddToHomescreenParams::AppType::SHORTCUT;
   params_->shortcut_info = std::make_unique<ShortcutInfo>(info);
   params_->primary_icon = data_fetcher_->primary_icon();
-  params_->has_maskable_primary_icon =
-      data_fetcher_->has_maskable_primary_icon();
   params_->install_source = InstallableMetrics::GetInstallSource(
       data_fetcher_->web_contents(), InstallTrigger::MENU);
   params_->installable_status = status_code;
diff --git a/components/webapps/browser/android/add_to_homescreen_params.cc b/components/webapps/browser/android/add_to_homescreen_params.cc
index 9a21377..b5f6252 100644
--- a/components/webapps/browser/android/add_to_homescreen_params.cc
+++ b/components/webapps/browser/android/add_to_homescreen_params.cc
@@ -16,4 +16,9 @@
                                    : native_app_package_name;
 }
 
+bool AddToHomescreenParams::HasMaskablePrimaryIcon() {
+  return app_type != AddToHomescreenParams::AppType::NATIVE &&
+         shortcut_info->is_primary_icon_maskable;
+}
+
 }  // namespace webapps
diff --git a/components/webapps/browser/android/add_to_homescreen_params.h b/components/webapps/browser/android/add_to_homescreen_params.h
index 0ed6656..e446ec1 100644
--- a/components/webapps/browser/android/add_to_homescreen_params.h
+++ b/components/webapps/browser/android/add_to_homescreen_params.h
@@ -31,7 +31,6 @@
 
   AppType app_type;
   SkBitmap primary_icon;
-  bool has_maskable_primary_icon = false;
   std::unique_ptr<ShortcutInfo> shortcut_info;
   WebappInstallSource install_source;
   InstallableStatusCode installable_status;
@@ -42,6 +41,7 @@
   ~AddToHomescreenParams();
 
   std::string GetAppIdentifier();
+  bool HasMaskablePrimaryIcon();
 };
 
 }  // namespace webapps
diff --git a/components/webapps/browser/android/ambient_badge_manager.cc b/components/webapps/browser/android/ambient_badge_manager.cc
index 8b16219..3b63848 100644
--- a/components/webapps/browser/android/ambient_badge_manager.cc
+++ b/components/webapps/browser/android/ambient_badge_manager.cc
@@ -228,7 +228,7 @@
       base::MakeRefCounted<segmentation_platform::InputContext>();
   input_context->metadata_args.emplace("url", validated_url_);
   input_context->metadata_args.emplace("maskable_icon",
-                                       a2hs_params_->has_maskable_primary_icon);
+                                       a2hs_params_->HasMaskablePrimaryIcon());
   segmentation_platform_service_->GetClassificationResult(
       segmentation_platform::kWebAppInstallationPromoKey, prediction_options,
       input_context,
@@ -312,11 +312,11 @@
           messages::kMessagesForAndroidInfrastructure)) {
     message_controller_.EnqueueMessage(
         web_contents_.get(), app_name_, a2hs_params_->primary_icon,
-        a2hs_params_->has_maskable_primary_icon, url);
+        a2hs_params_->HasMaskablePrimaryIcon(), url);
   } else {
     InstallableAmbientBadgeInfoBarDelegate::Create(
         web_contents_.get(), weak_factory_.GetWeakPtr(), app_name_,
-        a2hs_params_->primary_icon, a2hs_params_->has_maskable_primary_icon,
+        a2hs_params_->primary_icon, a2hs_params_->HasMaskablePrimaryIcon(),
         url);
   }
 }
diff --git a/components/webapps/browser/android/app_banner_manager_android.cc b/components/webapps/browser/android/app_banner_manager_android.cc
index 2ed893dc..d45262d 100644
--- a/components/webapps/browser/android/app_banner_manager_android.cc
+++ b/components/webapps/browser/android/app_banner_manager_android.cc
@@ -190,9 +190,9 @@
   if (native_app_data_.is_null()) {
     a2hs_params->app_type = AddToHomescreenParams::AppType::WEBAPK;
     a2hs_params->shortcut_info = ShortcutInfo::CreateShortcutInfo(
-        manifest_url_, manifest(), primary_icon_url_);
+        manifest_url_, manifest(), primary_icon_url_,
+        has_maskable_primary_icon_);
     a2hs_params->install_source = install_source;
-    a2hs_params->has_maskable_primary_icon = has_maskable_primary_icon_;
   } else {
     a2hs_params->app_type = AddToHomescreenParams::AppType::NATIVE;
     a2hs_params->native_app_data = native_app_data_;
diff --git a/components/webapps/browser/android/shortcut_info.cc b/components/webapps/browser/android/shortcut_info.cc
index 34c420f6..bf144cf 100644
--- a/components/webapps/browser/android/shortcut_info.cc
+++ b/components/webapps/browser/android/shortcut_info.cc
@@ -54,7 +54,8 @@
 std::unique_ptr<ShortcutInfo> ShortcutInfo::CreateShortcutInfo(
     const GURL& manifest_url,
     const blink::mojom::Manifest& manifest,
-    const GURL& primary_icon_url) {
+    const GURL& primary_icon_url,
+    bool primary_icon_maskable) {
   if (blink::IsEmptyManifest(manifest)) {
     return nullptr;
   }
@@ -63,6 +64,7 @@
   shortcut_info->UpdateFromManifest(manifest);
   shortcut_info->manifest_url = manifest_url;
   shortcut_info->best_primary_icon_url = primary_icon_url;
+  shortcut_info->is_primary_icon_maskable = primary_icon_maskable;
   shortcut_info->UpdateBestSplashIcon(manifest);
   return shortcut_info;
 }
diff --git a/components/webapps/browser/android/shortcut_info.h b/components/webapps/browser/android/shortcut_info.h
index f7d82c8..7223594 100644
--- a/components/webapps/browser/android/shortcut_info.h
+++ b/components/webapps/browser/android/shortcut_info.h
@@ -56,7 +56,8 @@
   static std::unique_ptr<ShortcutInfo> CreateShortcutInfo(
       const GURL& manifest_url,
       const blink::mojom::Manifest& manifest,
-      const GURL& primary_icon_url);
+      const GURL& primary_icon_url,
+      bool primary_icon_maskable);
 
   // This enum is used to back a UMA histogram, and must be treated as
   // append-only.
@@ -154,9 +155,10 @@
   absl::optional<SkColor> background_color;
   int ideal_splash_image_size_in_px = 0;
   int minimum_splash_image_size_in_px = 0;
+  GURL best_primary_icon_url;
+  bool is_primary_icon_maskable = false;
   GURL splash_image_url;
   bool is_splash_image_maskable = false;
-  GURL best_primary_icon_url;
   std::vector<std::string> icon_urls;
   std::vector<GURL> screenshot_urls;
   absl::optional<ShareTarget> share_target;
diff --git a/components/webapps/browser/android/webapk/webapk_proto_builder.cc b/components/webapps/browser/android/webapk/webapk_proto_builder.cc
index 99e826e..b3b25b0 100644
--- a/components/webapps/browser/android/webapk/webapk_proto_builder.cc
+++ b/components/webapps/browser/android/webapk/webapk_proto_builder.cc
@@ -95,7 +95,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
@@ -175,7 +174,7 @@
     webapk::Image* best_primary_icon_image = web_app_manifest->add_icons();
     best_primary_icon_image->set_image_data(primary_icon_data);
     best_primary_icon_image->add_usages(webapk::Image::PRIMARY_ICON);
-    if (is_primary_icon_maskable) {
+    if (shortcut_info.is_primary_icon_maskable) {
       best_primary_icon_image->add_purposes(webapk::Image::MASKABLE);
     } else {
       best_primary_icon_image->add_purposes(webapk::Image::ANY);
@@ -210,7 +209,7 @@
         image->set_image_data(it->second.unsafe_data);
       }
       image->add_usages(webapk::Image::PRIMARY_ICON);
-      if (is_primary_icon_maskable) {
+      if (shortcut_info.is_primary_icon_maskable) {
         image->add_purposes(webapk::Image::MASKABLE);
       } else {
         image->add_purposes(webapk::Image::ANY);
@@ -279,7 +278,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
@@ -291,8 +289,7 @@
   GetBackgroundTaskRunner()->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(&webapps::BuildProtoInBackground, shortcut_info, app_key,
-                     primary_icon_data, is_primary_icon_maskable,
-                     splash_icon_data, package_name, version,
+                     primary_icon_data, splash_icon_data, package_name, version,
                      std::move(icon_url_to_murmur2_hash), is_manifest_stale,
                      is_app_identity_update_supported,
                      std::vector<webapps::WebApkUpdateReason>()),
@@ -307,7 +304,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
@@ -319,9 +315,8 @@
                                                 base::BlockingType::MAY_BLOCK);
 
   std::unique_ptr<std::string> proto = BuildProtoInBackground(
-      shortcut_info, app_key, primary_icon_data, is_primary_icon_maskable,
-      splash_icon_data, package_name, version,
-      std::move(icon_url_to_murmur2_hash), is_manifest_stale,
+      shortcut_info, app_key, primary_icon_data, splash_icon_data, package_name,
+      version, std::move(icon_url_to_murmur2_hash), is_manifest_stale,
       is_app_identity_update_supported, std::move(update_reasons));
 
   // Create directory if it does not exist.
diff --git a/components/webapps/browser/android/webapk/webapk_proto_builder.h b/components/webapps/browser/android/webapk/webapk_proto_builder.h
index 94994a4..ce0b9d2a 100644
--- a/components/webapps/browser/android/webapk/webapk_proto_builder.h
+++ b/components/webapps/browser/android/webapk/webapk_proto_builder.h
@@ -31,7 +31,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
@@ -46,7 +45,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
@@ -64,7 +62,6 @@
     const webapps::ShortcutInfo& shortcut_info,
     const GURL& app_key,
     const std::string& primary_icon_data,
-    bool is_primary_icon_maskable,
     const std::string& splash_icon_data,
     const std::string& package_name,
     const std::string& version,
diff --git a/components/webapps/browser/android/webapk/webapk_proto_builder_unittest.cc b/components/webapps/browser/android/webapk/webapk_proto_builder_unittest.cc
index 91bf13c..5b14f15 100644
--- a/components/webapps/browser/android/webapk/webapk_proto_builder_unittest.cc
+++ b/components/webapps/browser/android/webapk/webapk_proto_builder_unittest.cc
@@ -68,8 +68,7 @@
       info.shortcut_items.back().icons.back().src = shortcut_url;
     }
 
-    webapps::BuildProto(info, app_key, primary_icon_data,
-                        false /* is_primary_icon_maskable */, splash_icon_data,
+    webapps::BuildProto(info, app_key, primary_icon_data, splash_icon_data,
                         "" /* package_name */, "" /* version */,
                         std::move(icon_url_to_murmur2_hash), is_manifest_stale,
                         is_app_identity_update_supported,
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
index afc7661..b66b10ad 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
@@ -773,7 +773,7 @@
                 byte[] responseSerialized =
                         Fido2CredentialRequestJni.get().makeCredentialResponseFromJson(json);
                 if (responseSerialized == null) {
-                    Log.e(TAG, "Failed to convert response from CredMan to Mojo object");
+                    Log.e(TAG, "Failed to convert response from CredMan to Mojo object: %s", json);
                     returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
                     return;
                 }
@@ -892,7 +892,7 @@
                 byte[] responseSerialized =
                         Fido2CredentialRequestJni.get().getCredentialResponseFromJson(json);
                 if (responseSerialized == null) {
-                    Log.e(TAG, "Failed to convert response from CredMan to Mojo object");
+                    Log.e(TAG, "Failed to convert response from CredMan to Mojo object: %s", json);
                     returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
                     return;
                 }
diff --git a/components/webauthn/json/value_conversions.cc b/components/webauthn/json/value_conversions.cc
index 6a1876a..9c3a6843 100644
--- a/components/webauthn/json/value_conversions.cc
+++ b/components/webauthn/json/value_conversions.cc
@@ -517,10 +517,12 @@
   for (const base::Value& transport_name : *transports) {
     absl::optional<device::FidoTransportProtocol> transport =
         FidoTransportProtocolFromValue(transport_name);
-    if (!transport) {
-      return InvalidMakeCredentialField("transports");
+    // Unknown transports are ignored because new transport values might be
+    // introduced in the future. Plausibly we should pass them as opaque
+    // strings, but our Mojo interface isn't shaped like that.
+    if (transport) {
+      response->transports.push_back(*transport);
     }
-    response->transports.push_back(*transport);
   }
 
   const base::Value::Dict* client_extension_results =
diff --git a/components/webauthn/json/value_conversions_unittest.cc b/components/webauthn/json/value_conversions_unittest.cc
index 0d07aff45..0bebb7b 100644
--- a/components/webauthn/json/value_conversions_unittest.cc
+++ b/components/webauthn/json/value_conversions_unittest.cc
@@ -211,7 +211,7 @@
   "response": {
     "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikJr1yeL5GN2Hx-qGxCrTE-CZwJpxBDHJqH9bgWFXhm0ZdAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQnqQEo7oPQjy-pupOzL3-bqCFjsQklxGpULfOrnG6WpQECAyYgASFYIJZF8F3hN5jYY05Slr0X96-zXsT0Za1-ZXjoRO69uRL1Ilgg8ZOMJTagPCfl8zZ1n36qxA8lIOOGvZrn1CahB9G-DAI",
     "clientDataJSON": "dGVzdCBjbGllbnQgZGF0YSBqc29u",
-    "transports": [ "usb" ]
+    "transports": [ "usb", "unknowntransport" ]
   },
   "type": "public-key"
 })";
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index af33753..918bb963f 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -2059,17 +2059,8 @@
     ::testing::ValuesIn(ui::AXInspectTestHelper::TreeTestPasses()),
     DumpAccessibilityTreeTestPassToString());
 
-// TODO(crbug.com/1428918): Re-enable this test on ChromeOS.
-// TODO(crbug.com/1440049): Fix flaky timeouts on Windows ASAN.
-#if BUILDFLAG(IS_CHROMEOS) || (BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER))
-#define MAYBE_AccessibilityFencedFrameScrollable \
-  DISABLED_AccessibilityFencedFrameScrollable
-#else
-#define MAYBE_AccessibilityFencedFrameScrollable \
-  AccessibilityFencedFrameScrollable
-#endif
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeFencedFrameTest,
-                       MAYBE_AccessibilityFencedFrameScrollable) {
+                       AccessibilityFencedFrameScrollable) {
   RunHtmlTest(FILE_PATH_LITERAL("fencedframe-scrollable-mparch.html"));
 }
 
diff --git a/content/browser/android/date_time_chooser_android.h b/content/browser/android/date_time_chooser_android.h
index 9131fe790..15cd5f8 100644
--- a/content/browser/android/date_time_chooser_android.h
+++ b/content/browser/android/date_time_chooser_android.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_ANDROID_DATE_TIME_CHOOSER_ANDROID_H_
 
 #include "base/android/jni_weak_ref.h"
+#include "base/gtest_prod_util.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "mojo/public/cpp/bindings/receiver.h"
diff --git a/content/browser/android/overscroll_controller_android_unittest.cc b/content/browser/android/overscroll_controller_android_unittest.cc
index bda4755..9afc218 100644
--- a/content/browser/android/overscroll_controller_android_unittest.cc
+++ b/content/browser/android/overscroll_controller_android_unittest.cc
@@ -34,8 +34,10 @@
 
 class MockCompositor : public WindowAndroidCompositor {
  public:
-  ~MockCompositor() override {}
-  std::unique_ptr<ReadbackRef> TakeReadbackRef() override { return nullptr; }
+  ~MockCompositor() override = default;
+  std::unique_ptr<ReadbackRef> TakeReadbackRef(const viz::SurfaceId&) override {
+    return nullptr;
+  }
   void RequestCopyOfOutputOnRootLayer(
       std::unique_ptr<viz::CopyOutputRequest>) override {}
   void SetNeedsAnimate() override {}
diff --git a/content/browser/attribution_reporting/attribution_debug_report.cc b/content/browser/attribution_reporting/attribution_debug_report.cc
index ad685732..314bea3 100644
--- a/content/browser/attribution_reporting/attribution_debug_report.cc
+++ b/content/browser/attribution_reporting/attribution_debug_report.cc
@@ -57,7 +57,6 @@
   kTriggerAggregateInsufficientBudget,
   kTriggerAggregateStorageLimit,
   kTriggerAggregateReportWindowPassed,
-  // TODO(crbug.com/1442939): Add an interop test for this.
   kTriggerAggregateExcessiveReports,
   kTriggerUnknownError,
 };
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index 5d36acd..e07382d 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -640,24 +640,24 @@
           .shadowRoot.querySelector('tbody');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 6 &&
-            table.children[0].children[3]?.innerText ===
+            table.children[0].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/report-event-attribution' &&
-            table.children[0].children[6]?.innerText === '13' &&
-            table.children[0].children[7]?.innerText === 'true' &&
-            table.children[0].children[2]?.innerText === 'Pending' &&
-            table.children[1].children[6]?.innerText === '11' &&
-            table.children[1].children[2]?.innerText ===
+            table.children[0].children[5]?.innerText === '13' &&
+            table.children[0].children[6]?.innerText === 'true' &&
+            table.children[0].children[1]?.innerText === 'Pending' &&
+            table.children[1].children[5]?.innerText === '11' &&
+            table.children[1].children[1]?.innerText ===
               'Replaced by higher-priority report: 21abd97f-73e8-4b88-9389-a9fee6abda5e' &&
-            table.children[2].children[6]?.innerText === '0' &&
-            table.children[2].children[7]?.innerText === 'false' &&
-            table.children[2].children[2]?.innerText === 'Sent: HTTP 200' &&
+            table.children[2].children[5]?.innerText === '0' &&
+            table.children[2].children[6]?.innerText === 'false' &&
+            table.children[2].children[1]?.innerText === 'Sent: HTTP 200' &&
             !table.children[2].classList.contains('send-error') &&
-            table.children[3].children[2]?.innerText === 'Prohibited by browser policy' &&
+            table.children[3].children[1]?.innerText === 'Prohibited by browser policy' &&
             !table.children[3].classList.contains('send-error') &&
-            table.children[4].children[2]?.innerText === 'Network error: ERR_METHOD_NOT_SUPPORTED' &&
+            table.children[4].children[1]?.innerText === 'Network error: ERR_METHOD_NOT_SUPPORTED' &&
             table.children[4].classList.contains('send-error') &&
-            table.children[5].children[2]?.innerText === 'Network error: ERR_TIMED_OUT' &&
-            table.children[5].children[3]?.innerText ===
+            table.children[5].children[1]?.innerText === 'Network error: ERR_TIMED_OUT' &&
+            table.children[5].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/debug/report-event-attribution') {
           obs.disconnect();
           document.title = $1;
@@ -683,21 +683,21 @@
           .shadowRoot.querySelector('tbody');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 6 &&
-            table.children[5].children[3]?.innerText ===
+            table.children[5].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/report-event-attribution' &&
-            table.children[5].children[6]?.innerText === '13' &&
-            table.children[5].children[7]?.innerText === 'true' &&
-            table.children[5].children[2]?.innerText === 'Pending' &&
-            table.children[4].children[6]?.innerText === '11' &&
-            table.children[4].children[2]?.innerText ===
+            table.children[5].children[5]?.innerText === '13' &&
+            table.children[5].children[6]?.innerText === 'true' &&
+            table.children[5].children[1]?.innerText === 'Pending' &&
+            table.children[4].children[5]?.innerText === '11' &&
+            table.children[4].children[1]?.innerText ===
               'Replaced by higher-priority report: 21abd97f-73e8-4b88-9389-a9fee6abda5e' &&
-            table.children[3].children[6]?.innerText === '0' &&
-            table.children[3].children[7]?.innerText === 'false' &&
-            table.children[3].children[2]?.innerText === 'Sent: HTTP 200' &&
-            table.children[2].children[2]?.innerText === 'Prohibited by browser policy' &&
-            table.children[1].children[2]?.innerText === 'Network error: ERR_METHOD_NOT_SUPPORTED' &&
-            table.children[0].children[2]?.innerText === 'Network error: ERR_TIMED_OUT' &&
-            table.children[0].children[3]?.innerText ===
+            table.children[3].children[5]?.innerText === '0' &&
+            table.children[3].children[6]?.innerText === 'false' &&
+            table.children[3].children[1]?.innerText === 'Sent: HTTP 200' &&
+            table.children[2].children[1]?.innerText === 'Prohibited by browser policy' &&
+            table.children[1].children[1]?.innerText === 'Network error: ERR_METHOD_NOT_SUPPORTED' &&
+            table.children[0].children[1]?.innerText === 'Network error: ERR_TIMED_OUT' &&
+            table.children[0].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/debug/report-event-attribution') {
           obs.disconnect();
           document.title = $1;
@@ -711,7 +711,7 @@
     // Sort by priority ascending.
     ASSERT_TRUE(ExecJsInWebUI(R"(
       document.querySelector('#reportTable')
-        .shadowRoot.querySelectorAll('th')[6].click();
+        .shadowRoot.querySelectorAll('th')[5].click();
     )"));
     ASSERT_EQ(kCompleteTitle2, title_watcher.WaitAndGetTitle());
   }
@@ -722,21 +722,21 @@
           .shadowRoot.querySelector('tbody');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 6 &&
-            table.children[0].children[3]?.innerText ===
+            table.children[0].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/report-event-attribution' &&
-            table.children[0].children[6]?.innerText === '13' &&
-            table.children[0].children[7]?.innerText === 'true' &&
-            table.children[0].children[2]?.innerText === 'Pending' &&
-            table.children[1].children[6]?.innerText === '11' &&
-            table.children[1].children[2]?.innerText ===
+            table.children[0].children[5]?.innerText === '13' &&
+            table.children[0].children[6]?.innerText === 'true' &&
+            table.children[0].children[1]?.innerText === 'Pending' &&
+            table.children[1].children[5]?.innerText === '11' &&
+            table.children[1].children[1]?.innerText ===
               'Replaced by higher-priority report: 21abd97f-73e8-4b88-9389-a9fee6abda5e' &&
-            table.children[2].children[6]?.innerText === '0' &&
-            table.children[2].children[7]?.innerText === 'false' &&
-            table.children[2].children[2]?.innerText === 'Sent: HTTP 200' &&
-            table.children[3].children[2]?.innerText === 'Prohibited by browser policy' &&
-            table.children[4].children[2]?.innerText === 'Network error: ERR_METHOD_NOT_SUPPORTED' &&
-            table.children[5].children[2]?.innerText === 'Network error: ERR_TIMED_OUT' &&
-            table.children[5].children[3]?.innerText ===
+            table.children[2].children[5]?.innerText === '0' &&
+            table.children[2].children[6]?.innerText === 'false' &&
+            table.children[2].children[1]?.innerText === 'Sent: HTTP 200' &&
+            table.children[3].children[1]?.innerText === 'Prohibited by browser policy' &&
+            table.children[4].children[1]?.innerText === 'Network error: ERR_METHOD_NOT_SUPPORTED' &&
+            table.children[5].children[1]?.innerText === 'Network error: ERR_TIMED_OUT' &&
+            table.children[5].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/debug/report-event-attribution') {
           obs.disconnect();
           document.title = $1;
@@ -750,7 +750,7 @@
     // Sort by priority descending.
     ASSERT_TRUE(ExecJsInWebUI(R"(
       document.querySelector('#reportTable')
-        .shadowRoot.querySelectorAll('th')[6].click();
+        .shadowRoot.querySelectorAll('th')[5].click();
     )"));
     ASSERT_EQ(kCompleteTitle3, title_watcher.WaitAndGetTitle());
   }
@@ -799,8 +799,8 @@
 
     const setTitleIfDone = (_, obs) => {
       if (table.children.length === 2 &&
-          table.children[0].children[6]?.innerText === '7' &&
-          table.children[1].children[2]?.innerText === 'Sent: HTTP 200') {
+          table.children[0].children[5]?.innerText === '7' &&
+          table.children[1].children[1]?.innerText === 'Sent: HTTP 200') {
         if (obs) {
           obs.disconnect();
         }
@@ -930,7 +930,7 @@
         .shadowRoot.querySelector('tbody');
     const setTitleIfDone = (_, obs) => {
       if (table.children.length === 1 &&
-          table.children[0].children[6]?.innerText === '7') {
+          table.children[0].children[5]?.innerText === '7') {
           if (obs) {
             obs.disconnect();
           }
@@ -1067,23 +1067,23 @@
           .shadowRoot.querySelector('tbody');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 7 &&
-            table.children[0].children[3]?.innerText ===
+            table.children[0].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/report-aggregate-attribution' &&
-            table.children[0].children[2]?.innerText === 'Pending' &&
-            table.children[0].children[6]?.innerText === '[ {  "key": "0x1",  "value": 2 }]' &&
-            table.children[0].children[7]?.innerText === '' &&
-            table.children[0].children[8]?.innerText === 'aws-cloud' &&
-            table.children[0].children[9]?.innerText === 'false' &&
-            table.children[1].children[2]?.innerText === 'Sent: HTTP 200' &&
-            table.children[1].children[7]?.innerText === 'abc' &&
-            table.children[2].children[2]?.innerText === 'Prohibited by browser policy' &&
-            table.children[3].children[2]?.innerText === 'Dropped due to assembly failure' &&
-            table.children[4].children[2]?.innerText === 'Network error: ERR_INVALID_REDIRECT' &&
-            table.children[5].children[2]?.innerText === 'Network error: ERR_INTERNET_DISCONNECTED' &&
-            table.children[5].children[3]?.innerText ===
+            table.children[0].children[1]?.innerText === 'Pending' &&
+            table.children[0].children[5]?.innerText === '[ {  "key": "0x1",  "value": 2 }]' &&
+            table.children[0].children[6]?.innerText === '' &&
+            table.children[0].children[7]?.innerText === 'aws-cloud' &&
+            table.children[0].children[8]?.innerText === 'false' &&
+            table.children[1].children[1]?.innerText === 'Sent: HTTP 200' &&
+            table.children[1].children[6]?.innerText === 'abc' &&
+            table.children[2].children[1]?.innerText === 'Prohibited by browser policy' &&
+            table.children[3].children[1]?.innerText === 'Dropped due to assembly failure' &&
+            table.children[4].children[1]?.innerText === 'Network error: ERR_INVALID_REDIRECT' &&
+            table.children[5].children[1]?.innerText === 'Network error: ERR_INTERNET_DISCONNECTED' &&
+            table.children[5].children[2]?.innerText ===
               'https://report.test/.well-known/attribution-reporting/debug/report-aggregate-attribution' &&
-            table.children[6].children[6]?.innerText === '[ {  "key": "0x0",  "value": 0 }]' &&
-            table.children[6].children[9]?.innerText === 'true') {
+            table.children[6].children[5]?.innerText === '[ {  "key": "0x0",  "value": 0 }]' &&
+            table.children[6].children[8]?.innerText === 'true') {
           obs.disconnect();
           document.title = $1;
         }
@@ -1309,8 +1309,8 @@
       const label = document.querySelector('#show-debug-event-reports span');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 2 &&
-            table.children[0].children[6]?.innerText === '1' &&
-            table.children[1].children[6]?.innerText === '2' &&
+            table.children[0].children[5]?.innerText === '1' &&
+            table.children[1].children[5]?.innerText === '2' &&
             label.innerText === '') {
           obs.disconnect();
           document.title = $1;
@@ -1348,7 +1348,7 @@
       const label = document.querySelector('#show-debug-event-reports span');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 1 &&
-            table.children[0].children[6]?.innerText === '2' &&
+            table.children[0].children[5]?.innerText === '2' &&
             label.innerText === ' (2 hidden)') {
           obs.disconnect();
           document.title = $1;
@@ -1377,9 +1377,9 @@
       const label = document.querySelector('#show-debug-event-reports span');
       const obs = new MutationObserver((_, obs) => {
         if (table.children.length === 3 &&
-            table.children[0].children[6]?.innerText === '1' &&
-            table.children[1].children[6]?.innerText === '2' &&
-            table.children[2].children[6]?.innerText === '3' &&
+            table.children[0].children[5]?.innerText === '1' &&
+            table.children[1].children[5]?.innerText === '2' &&
+            table.children[2].children[5]?.innerText === '3' &&
             label.innerText === '') {
           obs.disconnect();
           document.title = $1;
diff --git a/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc b/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc
index c74d2bf..24f9cc2 100644
--- a/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc
@@ -46,9 +46,9 @@
   return out << ",debug_permission=" << e.debug_permission << "}";
 }
 
-bool operator==(AttributionConfig::RateLimitConfig a,
-                AttributionConfig::RateLimitConfig b) {
-  const auto tie = [](AttributionConfig::RateLimitConfig config) {
+bool operator==(const AttributionConfig::RateLimitConfig& a,
+                const AttributionConfig::RateLimitConfig& b) {
+  const auto tie = [](const AttributionConfig::RateLimitConfig& config) {
     return std::make_tuple(
         config.time_window, config.max_source_registration_reporting_origins,
         config.max_attribution_reporting_origins, config.max_attributions);
@@ -56,9 +56,9 @@
   return tie(a) == tie(b);
 }
 
-bool operator==(AttributionConfig::EventLevelLimit a,
-                AttributionConfig::EventLevelLimit b) {
-  const auto tie = [](AttributionConfig::EventLevelLimit config) {
+bool operator==(const AttributionConfig::EventLevelLimit& a,
+                const AttributionConfig::EventLevelLimit& b) {
+  const auto tie = [](const AttributionConfig::EventLevelLimit& config) {
     return std::make_tuple(config.navigation_source_trigger_data_cardinality,
                            config.event_source_trigger_data_cardinality,
                            config.randomized_response_epsilon,
@@ -71,9 +71,9 @@
   return tie(a) == tie(b);
 }
 
-bool operator==(AttributionConfig::AggregateLimit a,
-                AttributionConfig::AggregateLimit b) {
-  const auto tie = [](AttributionConfig::AggregateLimit config) {
+bool operator==(const AttributionConfig::AggregateLimit& a,
+                const AttributionConfig::AggregateLimit& b) {
+  const auto tie = [](const AttributionConfig::AggregateLimit& config) {
     return std::make_tuple(
         config.max_reports_per_destination,
         config.aggregatable_budget_per_source, config.min_delay,
@@ -85,8 +85,8 @@
   return tie(a) == tie(b);
 }
 
-bool operator==(AttributionConfig a, AttributionConfig b) {
-  const auto tie = [](AttributionConfig config) {
+bool operator==(const AttributionConfig& a, const AttributionConfig& b) {
+  const auto tie = [](const AttributionConfig& config) {
     return std::make_tuple(config.max_sources_per_origin, config.rate_limit,
                            config.event_level_limit, config.aggregate_limit);
   };
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index c1cbc0c9..ac1cd23 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -577,12 +577,12 @@
   }
 
   // TODO(csharrison): Consider enforcing this limit after checking metrics.
-  bool allowed_by_destination_window = throttler_.UpdateAndGetAllowed(
+  DestinationThrottler::Result throttle_result = throttler_.UpdateAndGetResult(
       source.registration().destination_set,
       net::SchemefulSite(source.common_info().source_origin()),
       net::SchemefulSite(source.common_info().reporting_origin()));
-  base::UmaHistogramBoolean("Conversions.SourceAllowedByDestinationWindowLimit",
-                            allowed_by_destination_window);
+  base::UmaHistogramEnumeration("Conversions.DestinationThrottlerResult",
+                                throttle_result);
 
   MaybeEnqueueEvent(std::move(source));
 }
@@ -922,14 +922,13 @@
 }
 
 void AttributionManagerImpl::GetAllDataKeys(
-    base::OnceCallback<void(std::vector<AttributionManager::DataKey>)>
-        callback) {
+    base::OnceCallback<void(std::set<DataKey>)> callback) {
   attribution_storage_.AsyncCall(&AttributionStorage::GetAllDataKeys)
       .Then(std::move(callback));
 }
 
 void AttributionManagerImpl::RemoveAttributionDataByDataKey(
-    const AttributionManager::DataKey& data_key,
+    const DataKey& data_key,
     base::OnceClosure callback) {
   attribution_storage_.AsyncCall(&AttributionStorage::DeleteByDataKey)
       .WithArgs(data_key)
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 95dd6b3..fe279a32 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -142,7 +142,7 @@
       attribution_reporting::mojom::SourceRegistrationError) override;
 
   void GetAllDataKeys(
-      base::OnceCallback<void(std::vector<DataKey>)> callback) override;
+      base::OnceCallback<void(std::set<DataKey>)> callback) override;
 
   void RemoveAttributionDataByDataKey(const DataKey& data_key,
                                       base::OnceClosure callback) override;
diff --git a/content/browser/attribution_reporting/attribution_storage.h b/content/browser/attribution_reporting/attribution_storage.h
index 2789a1d..d8fe58c7 100644
--- a/content/browser/attribution_reporting/attribution_storage.h
+++ b/content/browser/attribution_reporting/attribution_storage.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_H_
 #define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_H_
 
+#include <set>
 #include <vector>
 
 #include "base/functional/callback_forward.h"
@@ -74,10 +75,10 @@
   // a negative number for no limit.
   virtual std::vector<StoredSource> GetActiveSources(int limit = -1) = 0;
 
-  // Returns all distinct reporting_origins as DataKeys for the
+  // Returns all distinct reporting origins for the
   // Browsing Data Model. Partial data will still be returned
   // in the event of an error.
-  virtual std::vector<AttributionDataModel::DataKey> GetAllDataKeys() = 0;
+  virtual std::set<AttributionDataModel::DataKey> GetAllDataKeys() = 0;
 
   // Deletes all data in storage for storage keys matching the provided
   // reporting origin in the data key.
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index a5505b7..5175883 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -9,6 +9,7 @@
 #include <functional>
 #include <iterator>
 #include <limits>
+#include <set>
 #include <string>
 #include <tuple>
 #include <utility>
@@ -3039,7 +3040,7 @@
       random_report.data());
 }
 
-std::vector<AttributionDataModel::DataKey>
+std::set<AttributionDataModel::DataKey>
 AttributionStorageSql::GetAllDataKeys() {
   // We don't bother creating the DB here if it doesn't exist, because it's not
   // possible for there to be any data to return if there's no DB
@@ -3048,7 +3049,7 @@
     return {};
   }
 
-  std::vector<AttributionDataModel::DataKey> keys;
+  std::set<AttributionDataModel::DataKey> keys;
 
   const auto get_data_keys = [&](sql::Statement& statement) {
     while (statement.Step()) {
@@ -3057,7 +3058,7 @@
       if (reporting_origin.opaque()) {
         continue;
       }
-      keys.emplace_back(std::move(reporting_origin));
+      keys.emplace(std::move(reporting_origin));
     }
   };
 
@@ -3070,8 +3071,7 @@
   get_data_keys(null_reports_statement);
 
   rate_limit_table_.AppendRateLimitDataKeys(&db_, keys);
-  return base::flat_set<AttributionDataModel::DataKey>(std::move(keys))
-      .extract();
+  return keys;
 }
 
 void AttributionStorageSql::DeleteByDataKey(
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.h b/content/browser/attribution_reporting/attribution_storage_sql.h
index c0c405a..bda68295 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.h
+++ b/content/browser/attribution_reporting/attribution_storage_sql.h
@@ -113,7 +113,7 @@
   std::vector<AttributionReport> GetReports(
       const std::vector<AttributionReport::Id>& ids) override;
   std::vector<StoredSource> GetActiveSources(int limit = -1) override;
-  std::vector<AttributionDataModel::DataKey> GetAllDataKeys() override;
+  std::set<AttributionDataModel::DataKey> GetAllDataKeys() override;
   void DeleteByDataKey(const AttributionDataModel::DataKey& datakey) override;
   bool DeleteReport(AttributionReport::Id report_id) override;
   bool UpdateReportForSendFailure(AttributionReport::Id report_id,
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
index 99dd39b..433b66b 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
@@ -9,6 +9,7 @@
 #include <functional>
 #include <limits>
 #include <memory>
+#include <set>
 #include <string>
 #include <utility>
 #include <vector>
@@ -42,6 +43,7 @@
 #include "content/browser/attribution_reporting/store_source_result.h"
 #include "content/browser/attribution_reporting/stored_source.h"
 #include "content/browser/attribution_reporting/test/configurable_storage_delegate.h"
+#include "content/public/browser/attribution_data_model.h"
 #include "net/base/schemeful_site.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/trigger_verification.h"
@@ -951,11 +953,12 @@
           .Build();
   storage()->MaybeCreateAndStoreReport(trigger);
 
-  std::vector keys = storage()->GetAllDataKeys();
+  std::set<AttributionDataModel::DataKey> keys = storage()->GetAllDataKeys();
   ASSERT_THAT(keys, SizeIs(2));
 
-  storage()->DeleteByDataKey(keys[0]);
-  storage()->DeleteByDataKey(keys[1]);
+  for (const auto& key : keys) {
+    storage()->DeleteByDataKey(key);
+  }
 
   CloseDatabase();
 
diff --git a/content/browser/attribution_reporting/destination_throttler.cc b/content/browser/attribution_reporting/destination_throttler.cc
index d93a56e6c..c9a44cf1 100644
--- a/content/browser/attribution_reporting/destination_throttler.cc
+++ b/content/browser/attribution_reporting/destination_throttler.cc
@@ -56,7 +56,7 @@
   SourceSiteData(SourceSiteData&) = delete;
   SourceSiteData& operator=(SourceSiteData&) = delete;
 
-  bool UpdateAndGetAllowed(
+  Result UpdateAndGetResult(
       const attribution_reporting::DestinationSet& destinations,
       const net::SchemefulSite& reporting_site,
       const Policy& policy) {
@@ -65,8 +65,9 @@
     auto& reporting_set = reporting_destinations_[reporting_site];
 
     // First detect whether we have capacity for _all_ the destinations.
-    if (!HasCapacity(destinations, reporting_set, policy)) {
-      return false;
+    Result throttle_result = HasCapacity(destinations, reporting_set, policy);
+    if (throttle_result != Result::kAllowed) {
+      return throttle_result;
     }
 
     // Mutate the data structure only after guaranteeing capacity to avoid
@@ -80,7 +81,7 @@
       }
       reporting_set.insert(it);
     }
-    return true;
+    return throttle_result;
   }
 
   bool AllEntriesOlderThan(base::TimeTicks time) const {
@@ -88,9 +89,9 @@
   }
 
  private:
-  bool HasCapacity(const attribution_reporting::DestinationSet& destinations,
-                   const DestinationSubset& reporting_set,
-                   const Policy& policy) {
+  Result HasCapacity(const attribution_reporting::DestinationSet& destinations,
+                     const DestinationSubset& reporting_set,
+                     const Policy& policy) {
     int all_capacity = policy.max_total - destinations_.size();
     int reporting_capacity =
         policy.max_per_reporting_site - reporting_set.size();
@@ -103,7 +104,16 @@
         reporting_capacity--;
       }
     }
-    return all_capacity >= 0 && reporting_capacity >= 0;
+    if (all_capacity >= 0 && reporting_capacity >= 0) {
+      return Result::kAllowed;
+    }
+    if (all_capacity < 0 && reporting_capacity < 0) {
+      return Result::kHitBothLimits;
+    }
+    if (all_capacity < 0) {
+      return Result::kHitGlobalLimit;
+    }
+    return Result::kHitReportingLimit;
   }
 
   void EvictEntriesOlderThan(base::TimeTicks time) {
@@ -130,14 +140,14 @@
 
 DestinationThrottler::~DestinationThrottler() = default;
 
-bool DestinationThrottler::UpdateAndGetAllowed(
+DestinationThrottler::Result DestinationThrottler::UpdateAndGetResult(
     const attribution_reporting::DestinationSet& destinations,
     const net::SchemefulSite& source_site,
     const net::SchemefulSite& reporting_site) {
   CleanUpOldEntries();
   auto it = source_site_data_.try_emplace(source_site, policy_);
-  return it.first->second.UpdateAndGetAllowed(destinations, reporting_site,
-                                              policy_);
+  return it.first->second.UpdateAndGetResult(destinations, reporting_site,
+                                             policy_);
 }
 
 void DestinationThrottler::CleanUpOldEntries() {
diff --git a/content/browser/attribution_reporting/destination_throttler.h b/content/browser/attribution_reporting/destination_throttler.h
index fd1afc4..17ff55e1 100644
--- a/content/browser/attribution_reporting/destination_throttler.h
+++ b/content/browser/attribution_reporting/destination_throttler.h
@@ -35,10 +35,26 @@
   DestinationThrottler(DestinationThrottler&) = delete;
   DestinationThrottler& operator=(DestinationThrottler&) = delete;
 
-  // Returns true if the throttler allowed `destinations` through. Also updates
-  // the internal state of the throttler to track all of the passed
-  // destinations.
-  [[nodiscard]] bool UpdateAndGetAllowed(
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class Result {
+    kAllowed = 0,
+    kHitGlobalLimit = 1,
+    kHitReportingLimit = 2,
+    kHitBothLimits = 3,
+    kMaxValue = kHitBothLimits
+  };
+  // - Returns `kAllowed` if the throttler allowed `destinations` through.
+  // - Return `kHitGlobalLimit` if `destinations` are not allowed due to the
+  //   global limit of `max_total`.
+  // - Returns `kHitReportingLimit` if `destinations` are not allowed due to the
+  //   `max_per_reporting_site` limit.
+  // - Returns `kHitBothLimits` if `destinations` are not allowed to to both
+  //   limits simultaneously.
+  //
+  // Also updates the internal state of the throttler to track all of the
+  // destinations, if allowed.
+  [[nodiscard]] Result UpdateAndGetResult(
       const attribution_reporting::DestinationSet& destinations,
       const net::SchemefulSite& source_site,
       const net::SchemefulSite& reporting_site);
diff --git a/content/browser/attribution_reporting/destination_throttler_unittest.cc b/content/browser/attribution_reporting/destination_throttler_unittest.cc
index 3f73c23..83e2907 100644
--- a/content/browser/attribution_reporting/destination_throttler_unittest.cc
+++ b/content/browser/attribution_reporting/destination_throttler_unittest.cc
@@ -39,6 +39,8 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 };
 
+using Result = DestinationThrottler::Result;
+
 TEST_F(DestinationThrottlerTest, SourceLimits) {
   DestinationThrottler::Policy policy{
       .max_total = 2,
@@ -47,20 +49,28 @@
   DestinationThrottler throttler(policy);
 
   // First source site:
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source1.test"), Site("report.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source1.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source1.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source1.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source1.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitGlobalLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo3.test"}),
+                                   Site("source1.test"), Site("report.test")));
 
   // Second source site should be independent:
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source2.test"), Site("report.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source2.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source2.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source2.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source2.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitGlobalLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo3.test"}),
+                                   Site("source2.test"), Site("report.test")));
 }
 
 TEST_F(DestinationThrottlerTest, ReportingSitesLimits) {
@@ -70,20 +80,28 @@
   DestinationThrottler throttler(policy);
 
   // First reporting site:
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source.test"), Site("report1.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source.test"), Site("report1.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source.test"), Site("report1.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report1.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source.test"), Site("report1.test")));
+  EXPECT_EQ(
+      Result::kHitReportingLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo3.test"}),
+                                   Site("source.test"), Site("report1.test")));
 
   // Second reporting site should be independent:
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source.test"), Site("report2.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source.test"), Site("report2.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source.test"), Site("report2.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report2.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source.test"), Site("report2.test")));
+  EXPECT_EQ(
+      Result::kHitReportingLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo3.test"}),
+                                   Site("source.test"), Site("report2.test")));
 }
 
 TEST_F(DestinationThrottlerTest, MultipleOverLimit) {
@@ -93,16 +111,21 @@
       .rate_limit_window = base::Minutes(1)};
   DestinationThrottler throttler(policy);
 
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test", "foo2.test", "foo3.test"}),
-      Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kHitGlobalLimit,
+            throttler.UpdateAndGetResult(
+                Destinations({"foo1.test", "foo2.test", "foo3.test"}),
+                Site("source.test"), Site("report.test")));
 
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitGlobalLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo3.test"}),
+                                   Site("source.test"), Site("report.test")));
 }
 
 TEST_F(DestinationThrottlerTest, RollingWindow) {
@@ -113,30 +136,38 @@
   DestinationThrottler throttler(policy);
 
   // Time 0s
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report.test")));
 
   task_environment_.FastForwardBy(base::Seconds(30));
 
   // Time 30s
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source.test"), Site("report.test")));
 
   task_environment_.FastForwardBy(base::Seconds(31));
 
   // Time 1:01. foo1.test should be evicted.
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo4.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo3.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitGlobalLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo4.test"}),
+                                   Site("source.test"), Site("report.test")));
 
   task_environment_.FastForwardBy(base::Seconds(30));
 
   // Time 1:31. foo2.test should be evicted.
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo5.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo6.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo5.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitGlobalLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo6.test"}),
+                                   Site("source.test"), Site("report.test")));
 }
 
 // Insert a new element once all previous elements are too old.
@@ -147,20 +178,28 @@
   DestinationThrottler throttler(policy);
 
   // Time 0s
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo1.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo2.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo2.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitReportingLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo3.test"}),
+                                   Site("source.test"), Site("report.test")));
 
   task_environment_.FastForwardBy(base::Seconds(61));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo4.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo5.test"}), Site("source.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo6.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo4.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo5.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitReportingLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo6.test"}),
+                                   Site("source.test"), Site("report.test")));
 }
 
 // Exercises EvictEntriesOlderThan with multiple deleted entries.
@@ -171,23 +210,44 @@
       .rate_limit_window = base::Minutes(1)};
   DestinationThrottler throttler(policy);
 
-  EXPECT_TRUE(
-      throttler.UpdateAndGetAllowed(Destinations({"foo1.test", "foo2.test"}),
-                                    Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test", "foo2.test"}),
+                                  Site("source.test"), Site("report.test")));
 
   task_environment_.FastForwardBy(base::Seconds(31));
 
-  EXPECT_TRUE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo3.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo3.test"}),
+                                  Site("source.test"), Site("report.test")));
 
   task_environment_.FastForwardBy(base::Seconds(30));
 
   // Should evict the first two destinations.
-  EXPECT_TRUE(
-      throttler.UpdateAndGetAllowed(Destinations({"foo4.test", "foo5.test"}),
-                                    Site("source.test"), Site("report.test")));
-  EXPECT_FALSE(throttler.UpdateAndGetAllowed(
-      Destinations({"foo6.test"}), Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo4.test", "foo5.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitGlobalLimit,
+      throttler.UpdateAndGetResult(Destinations({"foo6.test"}),
+                                   Site("source.test"), Site("report.test")));
+}
+
+TEST_F(DestinationThrottlerTest, HitBothLimits) {
+  DestinationThrottler::Policy policy{.max_total = 1,
+                                      .max_per_reporting_site = 1,
+                                      .rate_limit_window = base::Minutes(1)};
+  DestinationThrottler throttler(policy);
+
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(Result::kAllowed, throttler.UpdateAndGetResult(
+                                  Destinations({"foo1.test"}),
+                                  Site("source.test"), Site("report.test")));
+  EXPECT_EQ(
+      Result::kHitBothLimits,
+      throttler.UpdateAndGetResult(Destinations({"foo2.test"}),
+                                   Site("source.test"), Site("report.test")));
 }
 
 }  // namespace
diff --git a/content/browser/attribution_reporting/rate_limit_table.cc b/content/browser/attribution_reporting/rate_limit_table.cc
index 47ff8ada7..632d0bfb 100644
--- a/content/browser/attribution_reporting/rate_limit_table.cc
+++ b/content/browser/attribution_reporting/rate_limit_table.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/attribution_reporting/rate_limit_table.h"
 
+#include <set>
 #include <string>
 #include <vector>
 
@@ -457,7 +458,7 @@
 
 void RateLimitTable::AppendRateLimitDataKeys(
     sql::Database* db,
-    std::vector<AttributionDataModel::DataKey>& keys) {
+    std::set<AttributionDataModel::DataKey>& keys) {
   sql::Statement statement(db->GetCachedStatement(
       SQL_FROM_HERE, attribution_queries::kGetRateLimitDataKeysSql));
 
@@ -466,7 +467,7 @@
     if (reporting_origin.opaque()) {
       continue;
     }
-    keys.emplace_back(std::move(reporting_origin));
+    keys.emplace(std::move(reporting_origin));
   }
 }
 
diff --git a/content/browser/attribution_reporting/rate_limit_table.h b/content/browser/attribution_reporting/rate_limit_table.h
index 0037930..158a766 100644
--- a/content/browser/attribution_reporting/rate_limit_table.h
+++ b/content/browser/attribution_reporting/rate_limit_table.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_RATE_LIMIT_TABLE_H_
 #define CONTENT_BROWSER_ATTRIBUTION_REPORTING_RATE_LIMIT_TABLE_H_
 
+#include <set>
 #include <vector>
 
 #include "base/containers/flat_set.h"
@@ -111,9 +112,8 @@
       sql::Database* db,
       const std::vector<StoredSource::Id>& source_ids);
 
-  void AppendRateLimitDataKeys(
-      sql::Database* db,
-      std::vector<AttributionDataModel::DataKey>& keys);
+  void AppendRateLimitDataKeys(sql::Database* db,
+                               std::set<AttributionDataModel::DataKey>& keys);
 
  private:
   [[nodiscard]] bool AddRateLimit(
diff --git a/content/browser/attribution_reporting/rate_limit_table_unittest.cc b/content/browser/attribution_reporting/rate_limit_table_unittest.cc
index 133de5e..9caf9e9 100644
--- a/content/browser/attribution_reporting/rate_limit_table_unittest.cc
+++ b/content/browser/attribution_reporting/rate_limit_table_unittest.cc
@@ -1003,7 +1003,7 @@
           .SetReportingOrigin(*SuitableOrigin::Deserialize("https://b.r.test"))
           .BuildStored()));
 
-  std::vector<AttributionDataModel::DataKey> keys;
+  std::set<AttributionDataModel::DataKey> keys;
   table_.AppendRateLimitDataKeys(&db_, keys);
 
   EXPECT_THAT(keys, ElementsAre(expected_1, expected_2));
diff --git a/content/browser/attribution_reporting/sql_queries.h b/content/browser/attribution_reporting/sql_queries.h
index 8caf7a0..2356341 100644
--- a/content/browser/attribution_reporting/sql_queries.h
+++ b/content/browser/attribution_reporting/sql_queries.h
@@ -66,17 +66,17 @@
     "SELECT dedup_key FROM dedup_keys WHERE source_id=? AND report_type=?";
 
 inline constexpr const char kGetSourcesDataKeysSql[] =
-    "SELECT DISTINCT reporting_origin FROM sources";
+    "SELECT reporting_origin FROM sources";
 
 static_assert(
     static_cast<int>(
         attribution_reporting::mojom::ReportType::kNullAggregatable) == 2,
     "update `report_type=2` clause below");
 inline constexpr const char kGetNullReportsDataKeysSql[] =
-    "SELECT DISTINCT reporting_origin FROM reports WHERE report_type=2";
+    "SELECT reporting_origin FROM reports WHERE report_type=2";
 
 inline constexpr const char kGetRateLimitDataKeysSql[] =
-    "SELECT DISTINCT reporting_origin FROM rate_limits";
+    "SELECT reporting_origin FROM rate_limits";
 
 inline constexpr const char kCountReportsForDestinationSql[] =
     "SELECT COUNT(*)FROM source_destinations D "
diff --git a/content/browser/attribution_reporting/test/mock_attribution_manager.h b/content/browser/attribution_reporting/test/mock_attribution_manager.h
index c88bc8b..d689645a 100644
--- a/content/browser/attribution_reporting/test/mock_attribution_manager.h
+++ b/content/browser/attribution_reporting/test/mock_attribution_manager.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include <memory>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -94,7 +95,7 @@
 
   MOCK_METHOD(void,
               GetAllDataKeys,
-              (base::OnceCallback<void(std::vector<DataKey>)>),
+              (base::OnceCallback<void(std::set<DataKey>)>),
               (override));
 
   MOCK_METHOD(void,
diff --git a/content/browser/browsing_data/browsing_data_remover_impl.cc b/content/browser/browsing_data/browsing_data_remover_impl.cc
index d430a07..d0e57b4 100644
--- a/content/browser/browsing_data/browsing_data_remover_impl.cc
+++ b/content/browser/browsing_data/browsing_data_remover_impl.cc
@@ -29,6 +29,7 @@
 #include "content/browser/browsing_data/browsing_data_filter_builder_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -560,6 +561,14 @@
     network_context->ClearCorsPreflightCache(
         filter_builder->BuildNetworkServiceFilter(),
         CreateTaskCompletionClosureForMojo(TracingDataType::kPreflightCache));
+
+    // Clears the BFCache entries for the current browser context.
+    for (WebContentsImpl* web_contents : WebContentsImpl::GetAllWebContents()) {
+      if (web_contents->GetBrowserContext()->UniqueId() ==
+          browser_context_->UniqueId()) {
+        web_contents->GetController().GetBackForwardCache().Flush();
+      }
+    }
   }
 
   //////////////////////////////////////////////////////////////////////////////
diff --git a/content/browser/browsing_data/browsing_data_remover_impl_browsertest.cc b/content/browser/browsing_data/browsing_data_remover_impl_browsertest.cc
index 747970f..4ab68fe 100644
--- a/content/browser/browsing_data/browsing_data_remover_impl_browsertest.cc
+++ b/content/browser/browsing_data/browsing_data_remover_impl_browsertest.cc
@@ -10,10 +10,14 @@
 #include "base/containers/contains.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
+#include "base/location.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "content/browser/back_forward_cache_test_util.h"
 #include "content/browser/browsing_data/shared_storage_clear_site_data_tester.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
@@ -23,6 +27,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/browsing_data_remover_test_util.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -90,7 +95,9 @@
 
 namespace content {
 
-class BrowsingDataRemoverImplBrowserTest : public ContentBrowserTest {
+class BrowsingDataRemoverImplBrowserTest
+    : public ContentBrowserTest,
+      public BackForwardCacheMetricsTestMatcher {
  public:
   BrowsingDataRemoverImplBrowserTest()
       : ssl_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {
@@ -104,7 +111,10 @@
     EXPECT_TRUE(ssl_server_.Start());
   }
 
-  void SetUpOnMainThread() override {}
+  void SetUpOnMainThread() override {
+    ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
+    histogram_tester_ = std::make_unique<base::HistogramTester>();
+  }
 
   void RemoveAndWait(uint64_t remove_mask) {
     content::BrowsingDataRemover* remover =
@@ -238,8 +248,21 @@
         ->GetNetworkContext();
   }
 
+  const ukm::TestAutoSetUkmRecorder& ukm_recorder() override {
+    return *ukm_recorder_;
+  }
+  const base::HistogramTester& histogram_tester() override {
+    return *histogram_tester_;
+  }
+
+  const net::test_server::EmbeddedTestServer& ssl_server() {
+    return ssl_server_;
+  }
+
  private:
   net::test_server::EmbeddedTestServer ssl_server_;
+  std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
+  std::unique_ptr<base::HistogramTester> histogram_tester_;
 };
 
 // Verify that TransportSecurityState data is cleared for REMOVE_CACHE.
@@ -294,6 +317,32 @@
   EXPECT_FALSE(IsHttpAuthCacheSet());
 }
 
+// Disabled. See https://crbug.com/1444976
+IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
+                       DISABLED_ClearBackForwardCacheEntries) {
+  GURL url_1 = ssl_server().GetURL("/title1.html");
+  GURL url_2 = ssl_server().GetURL("/title2.html");
+
+  // 1) Navigate to url_1, then to url_2.
+  ASSERT_TRUE(NavigateToURL(shell(), url_1));
+  ASSERT_TRUE(NavigateToURL(shell(), url_2));
+
+  // 2) Go back, the page should be restored from BFCache.
+  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
+  ExpectRestored(FROM_HERE);
+
+  // 3) Navigate to url_2 again.
+  ASSERT_TRUE(NavigateToURL(shell(), url_2));
+
+  // 4) Remove the browsing data with DATA_TYPE_CACHE and go back, the page
+  // should not be restored from BFCache since the BFCache entry should be
+  // flushed.
+  RemoveAndWait(BrowsingDataRemover::DATA_TYPE_CACHE);
+  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
+  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kCacheFlushed},
+                    {}, {}, {}, {}, FROM_HERE);
+}
+
 class CookiesBrowsingDataRemoverImplBrowserTest
     : public BrowsingDataRemoverImplBrowserTest {
  public:
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index 5ba8e11..191b1f3 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -307,10 +307,6 @@
   return result;
 }
 
-CoopRelatedGroupId BrowsingInstance::GetCoopRelatedGroupId() {
-  return coop_related_group_->GetId();
-}
-
 size_t BrowsingInstance::GetCoopRelatedGroupActiveContentsCount() {
   return coop_related_group_->active_contents_count();
 }
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h
index b9e75e2..497aba8 100644
--- a/content/browser/browsing_instance.h
+++ b/content/browser/browsing_instance.h
@@ -209,9 +209,16 @@
   // BrowsingInstance.
   void UnregisterSiteInstance(SiteInstanceImpl* site_instance);
 
-  // Returns the Id of the CoopRelatedGroup to which this BrowsingInstance
-  // belongs.
-  CoopRelatedGroupId GetCoopRelatedGroupId();
+  // Returns the token uniquely identifying the CoopRelatedGroup this
+  // BrowsingInstance belongs to. This might be used in the renderer, as opposed
+  // to IDs.
+  base::UnguessableToken coop_related_group_token() const {
+    return coop_related_group_->token();
+  }
+
+  // Returns the token uniquely identifying this BrowsingInstance. See member
+  // declaration for more context.
+  base::UnguessableToken token() const { return token_; }
 
   // Returns the total number of WebContents either living in this
   // BrowsingInstance or that can communicate with it via the CoopRelatedGroup.
@@ -338,6 +345,14 @@
   // cross-origin iframes are opened with no-opener. Once COOP inheritance for
   // those cases is figured out, change the mentions of origin to "COOP origin".
   absl::optional<url::Origin> common_coop_origin_;
+
+  // A token uniquely identifying this BrowsingInstance. This is used in case we
+  // need this information available in the renderer process, rather than
+  // sending an ID. Both IDs and Tokens are necessary, because some parts of the
+  // process model use the ordering of the IDs, that cannot be provided by
+  // tokens alone. Also note that IDs are defined in IsolationContext while
+  // tokens are more conveniently defined here.
+  const base::UnguessableToken token_ = base::UnguessableToken::Create();
 };
 
 }  // namespace content
diff --git a/content/browser/coop_related_group.cc b/content/browser/coop_related_group.cc
index e871e3b..58301ed 100644
--- a/content/browser/coop_related_group.cc
+++ b/content/browser/coop_related_group.cc
@@ -11,24 +11,15 @@
 
 namespace content {
 
-// Start the CoopRelatedGroup ID counter from 1 to avoid a conflict with the
-// invalid CoopRelatedGroupId value, which is 0 in its underlying IdType32.
-int CoopRelatedGroup::next_coop_related_group_id_ = 1;
-
 CoopRelatedGroup::CoopRelatedGroup(BrowserContext* browser_context,
                                    bool is_guest,
                                    bool is_fenced)
-    : id_(CoopRelatedGroupId::FromUnsafeValue(next_coop_related_group_id_++)),
-      browser_context_(browser_context),
+    : browser_context_(browser_context),
       is_guest_(is_guest),
       is_fenced_(is_fenced) {}
 
 CoopRelatedGroup::~CoopRelatedGroup() = default;
 
-CoopRelatedGroupId CoopRelatedGroup::GetId() {
-  return id_;
-}
-
 scoped_refptr<BrowsingInstance>
 CoopRelatedGroup::FindSuitableBrowsingInstanceForCoopPolicy(
     const absl::optional<url::Origin>& common_coop_origin,
diff --git a/content/browser/coop_related_group.h b/content/browser/coop_related_group.h
index 2b49677f..3ca8fbb6 100644
--- a/content/browser/coop_related_group.h
+++ b/content/browser/coop_related_group.h
@@ -9,7 +9,6 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/types/id_type.h"
 #include "content/browser/url_info.h"
 #include "content/browser/web_exposed_isolation_info.h"
 #include "content/common/content_export.h"
@@ -56,8 +55,6 @@
 // the same CoopRelatedGroup but in a different BrowsingInstance, use
 // SiteInstanceImpl::GetCoopRelatedSiteInstance. Because of this,
 // CoopRelatedGroups are tested in site_instance_impl_unittest.cc.
-using CoopRelatedGroupId = base::IdType32<class CoopRelatedGroupIdTag>;
-
 class CONTENT_EXPORT CoopRelatedGroup final
     : public base::RefCounted<CoopRelatedGroup> {
  public:
@@ -74,8 +71,8 @@
                             bool is_fenced);
   ~CoopRelatedGroup();
 
-  // Returns the unique ID associated with this CoopRelatedGroup.
-  CoopRelatedGroupId GetId();
+  // Returns the token uniquely identifying this CoopRelatedGroup.
+  base::UnguessableToken token() const { return token_; }
 
   // Returns a SiteInstance in this CoopRelatedGroup, depending on the passed
   // `url_info`. It might reuse an existing BrowsingInstance that is part of the
@@ -120,12 +117,6 @@
     active_contents_count_--;
   }
 
-  // Members used to provide IDs to CoopRelatedGroup.
-  // `next_coop_related_group_id_` is used as a static generator, while `id_`
-  // represent this specific CoopRelatedGroup id.
-  static int next_coop_related_group_id_;
-  CoopRelatedGroupId id_;
-
   // Recorded with the first BrowsingInstance and used to create new
   // BrowsingInstances. All BrowsingInstances in a CoopRelatedGroup should share
   // the same BrowserContext, therefore recording it at creation time is fine.
@@ -164,6 +155,10 @@
   // group, to know whether certain actions (e.g. putting a page into the
   // BFCache) are allowed.
   size_t active_contents_count_{0u};
+
+  // A token uniquely identifying this CoopRelatedGroup. This can be sent to the
+  // renderer process if needed, without security risks.
+  const base::UnguessableToken token_ = base::UnguessableToken::Create();
 };
 
 }  // namespace content
diff --git a/content/browser/file_system_access/file_system_chooser_browsertest.cc b/content/browser/file_system_access/file_system_chooser_browsertest.cc
index 0dcb4376f..32aa18e 100644
--- a/content/browser/file_system_access/file_system_chooser_browsertest.cc
+++ b/content/browser/file_system_access/file_system_chooser_browsertest.cc
@@ -57,6 +57,10 @@
  public:
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+#if BUILDFLAG(IS_WIN)
+    // Convert path to long format to avoid mixing long and 8.3 formats in test.
+    ASSERT_TRUE(temp_dir_.Set(base::MakeLongFilePath(temp_dir_.Take())));
+#endif  // BUILDFLAG(IS_WIN)
 
     // Register an external mount point to test support for virtual paths.
     // This maps the virtual path a native local path to make these tests work
diff --git a/content/browser/hid/hid_service_unittest.cc b/content/browser/hid/hid_service_unittest.cc
index de4286bb..1e56ff33 100644
--- a/content/browser/hid/hid_service_unittest.cc
+++ b/content/browser/hid/hid_service_unittest.cc
@@ -1388,8 +1388,6 @@
 
   CheckHidServiceConnectedState(service_creation_type, false);
 
-  base::RunLoop run_loop;
-  mojo::Remote<device::mojom::HidConnection> connection;
   TestFuture<mojo::PendingRemote<device::mojom::HidConnection>>
       pending_remote_future;
   EXPECT_CALL(hid_delegate(), GetDeviceInfo)
@@ -1398,7 +1396,6 @@
   service->Connect(kTestGuid, std::move(hid_connection_client),
                    pending_remote_future.GetCallback());
   EXPECT_FALSE(pending_remote_future.Take());
-  run_loop.RunUntilIdle();
   CheckHidServiceConnectedState(service_creation_type, false);
 }
 
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index 9d4629c..d4e73cdb 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -527,6 +527,12 @@
   return base::StringPrintf(R"(
     function generateBid(interestGroup, auctionSignals, perBuyerSignals,
                          trustedBiddingSignals, browserSignals) {
+      privateAggregation.reportContributionForEvent("reserved.loss", {
+                bucket: {baseValue: "bid-reject-reason"},
+                value: 123,
+              });
+      forDebuggingOnly.reportAdAuctionLoss(
+            "https://loss.example.com/?rejectReason=${rejectReason}");
       return {ad: {},
               bid: %d,
               bidCurrency: "%s",
@@ -3206,6 +3212,12 @@
 }
 
 TEST_F(AuctionRunnerTest, BasicCurrencyCheck) {
+  // This feature only relevant for the debug_loss_report_urls portion of the
+  // test.
+  base::test::ScopedFeatureList debug_features;
+  debug_features.InitAndEnableFeature(
+      blink::features::kBiddingAndScoringDebugReportingAPI);
+
   // Test with bidder 2 making a bid with CAD when the fixture expects USD.
   auction_worklet::AddJavascriptResponse(
       &url_loader_factory_, kBidder1Url,
@@ -3227,6 +3239,24 @@
               testing::ElementsAre("https://anotheradthing.com/bids.js "
                                    "generateBid() bidCurrency mismatch;"
                                    " returned 'CAD', expected 'USD'."));
+  EXPECT_THAT(
+      result_.debug_loss_report_urls,
+      testing::ElementsAre("https://loss.example.com/"
+                           "?rejectReason=wrong-generate-bid-currency"));
+  EXPECT_THAT(
+      private_aggregation_manager_.TakePrivateAggregationRequests(),
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(BuildPrivateAggregationRequest(
+                            /*bucket=*/static_cast<uint64_t>(
+                                auction_worklet::mojom::RejectReason::
+                                    kWrongGenerateBidCurrency),
+                            /*value=*/123))),
+          testing::Pair(
+              // kBasicReportResult reports (7,8)
+              kSeller, ElementsAreRequests(
+                           BuildPrivateAggregationRequest(/*bucket=*/7,
+                                                          /*value=*/8)))));
 }
 
 TEST_F(AuctionRunnerTest, BasicCurrencyRedact) {
@@ -4140,11 +4170,20 @@
 // straight through by the component seller --- verifying it's checked against
 // sellerCurrency of the component auction.
 TEST_F(AuctionRunnerTest, ComponentAuctionCurrencyPassThroughCheck) {
+  // This feature only relevant for the debug_loss_report_urls portion of the
+  // test.
+  base::test::ScopedFeatureList debug_features;
+  debug_features.InitAndEnableFeature(
+      blink::features::kBiddingAndScoringDebugReportingAPI);
+
   const char kBidScript[] = R"(
     const inBid = %d;
     function generateBid(
         interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals,
         browserSignals) {
+      forDebuggingOnly.reportAdAuctionLoss(
+            "https://loss.example.com/?rejectReason=${rejectReason}&bid=" +
+            inBid);
       return {bid: inBid, bidCurrency: 'USD',
               render: interestGroup.ads[0].renderUrl,
               allowComponentAuction: true};
@@ -4159,6 +4198,10 @@
     const suffix = "%s";
     function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals,
                       browserSignals) {
+      privateAggregation.reportContributionForEvent("reserved.loss", {
+        bucket: {baseValue: "bid-reject-reason"},
+        value: 123 + bid,
+      });
       if (browserSignals.bidCurrency !== 'USD') {
         throw 'Wrong bidCurrency in scoreAd() for ' + suffix;
       }
@@ -4192,10 +4235,31 @@
   RunStandardAuction();
   const char kError[] =
       "https://component.seller1.test/foo.js scoreAd() bid passthrough "
-      "mismatch "
-      "vs own sellerCurrency, expected 'CAD' got 'USD'.";
+      "mismatch vs own sellerCurrency, expected 'CAD' got 'USD'.";
+
+  EXPECT_THAT(
+      private_aggregation_manager_.TakePrivateAggregationRequests(),
+      testing::UnorderedElementsAre(testing::Pair(
+          kComponentSeller1,
+          ElementsAreRequests(BuildPrivateAggregationRequest(
+                                  /*bucket=*/static_cast<uint64_t>(
+                                      auction_worklet::mojom::RejectReason::
+                                          kWrongScoreAdCurrency),
+                                  /*value=*/124),
+                              BuildPrivateAggregationRequest(
+                                  /*bucket=*/static_cast<uint64_t>(
+                                      auction_worklet::mojom::RejectReason::
+                                          kWrongScoreAdCurrency),
+                                  /*value=*/125)))));
+
   // Error is seen twice since it's relevant to two bids.
   EXPECT_THAT(result_.errors, testing::ElementsAre(kError, kError));
+  EXPECT_THAT(
+      result_.debug_loss_report_urls,
+      testing::ElementsAre("https://loss.example.com/"
+                           "?rejectReason=wrong-score-ad-currency&bid=1",
+                           "https://loss.example.com/"
+                           "?rejectReason=wrong-score-ad-currency&bid=2"));
   EXPECT_FALSE(result_.winning_group_id.has_value());
   EXPECT_FALSE(result_.ad_descriptor.has_value());
 }
@@ -4682,6 +4746,10 @@
   // it interpreted the incoming bid as 20 in sellerCurrency.
   const std::string kComponentSellerScript = R"(
     function scoreAd(adMetadata, bid, auctionConfig, browserSignals) {
+      privateAggregation.reportContributionForEvent("reserved.loss", {
+              bucket: {baseValue: "bid-reject-reason"},
+              value: 123,
+      });
       return {desirability: 10, allowComponentAuction: true, bid: 3,
               bidCurrency: 'USD', incomingBidInSellerCurrency: 20};
     }
@@ -4773,11 +4841,20 @@
                        .SetNumBidderWorklets(1)
                        .SetNumInterestGroupsWithOnlyNonKAnonBid(1));
     } else {
+      EXPECT_THAT(private_aggregation_manager_.TakePrivateAggregationRequests(),
+                  testing::UnorderedElementsAre(testing::Pair(
+                      kComponentSeller1,
+                      ElementsAreRequests(BuildPrivateAggregationRequest(
+                          /*bucket=*/static_cast<uint64_t>(
+                              auction_worklet::mojom::RejectReason::
+                                  kWrongScoreAdCurrency),
+                          /*value=*/123)))));
+
       EXPECT_THAT(result_.errors,
                   testing::ElementsAre(
                       "https://component.seller1.test/foo.js scoreAd() "
-                      "bidCurrency mismatch "
-                      "vs own sellerCurrency, expected 'CAD' got 'USD'."));
+                      "bidCurrency mismatch vs own sellerCurrency, "
+                      "expected 'CAD' got 'USD'."));
       EXPECT_FALSE(result_.ad_descriptor);
     }
   }
@@ -9390,6 +9467,8 @@
     blink::AdDescriptor ad_descriptor;
     absl::optional<std::vector<blink::AdDescriptor>> ad_component_descriptors;
     base::TimeDelta duration;
+    auction_worklet::mojom::RejectReason reject_reason =
+        auction_worklet::mojom::RejectReason::kNotAvailable;
   } kTestCases[] = {
       // Bids that aren't positive integers.
       {
@@ -9500,20 +9579,6 @@
           base::TimeDelta(),
       },
 
-      // HTTPS render URL with a size specification that does not match any
-      // allowed ad descriptors.
-      {
-          "Bid render ad must have a valid URL and size (if specified)",
-          1,
-          /*bid_currency=*/absl::nullopt,
-          blink::AdDescriptor(
-              GURL("https://ad1.com"),
-              blink::AdSize(100, blink::AdSize::LengthUnit::kPixels, 100,
-                            blink::AdSize::LengthUnit::kPixels)),
-          absl::nullopt,
-          base::TimeDelta(),
-      },
-
       // Invalid component URL.
       {
           "Bid ad component must have a valid URL and size (if specified)",
@@ -9570,19 +9635,6 @@
                             blink::AdSize::LengthUnit::kPixels))},
           base::TimeDelta(),
       },
-      // HTTPS component URL with a size specification that does not match any
-      // allowed ad descriptors.
-      {
-          "Bid ad component must have a valid URL and size (if specified)",
-          1,
-          /*bid_currency=*/absl::nullopt,
-          blink::AdDescriptor(GURL("https://ad1.com")),
-          std::vector<blink::AdDescriptor>{blink::AdDescriptor(
-              GURL("https://ad1.com-component1.com"),
-              blink::AdSize(100, blink::AdSize::LengthUnit::kPixels, 100,
-                            blink::AdSize::LengthUnit::kPixels))},
-          base::TimeDelta(),
-      },
 
       // Negative time.
       {
@@ -9593,6 +9645,13 @@
           absl::nullopt,
           base::Milliseconds(-1),
       },
+
+      // Reject reason validation.
+      {"Invalid bid reject_reason", 1,
+       /*bid_currency=*/absl::nullopt,
+       blink::AdDescriptor(GURL("https://ad2.com")), absl::nullopt,
+       base::Milliseconds(10),
+       auction_worklet::mojom::RejectReason::kCategoryExclusions},
   };
 
   for (const auto& test_case : kTestCases) {
@@ -9610,7 +9669,13 @@
     bidder1_worklet->InvokeGenerateBidCallback(
         test_case.bid, test_case.bid_currency, test_case.ad_descriptor,
         /*mojo_kanon_bid=*/nullptr, test_case.ad_component_descriptors,
-        test_case.duration);
+        test_case.duration, /*bidding_signals_data_version=*/
+        absl::nullopt, /*debug_loss_report_url=*/absl::nullopt,
+        /*debug_win_report_url=*/absl::nullopt,
+        /*pa_requests=*/{},
+        /*dependency_latencies=*/
+        auction_worklet::mojom::GenerateBidDependencyLatenciesPtr(),
+        test_case.reject_reason);
     // Bidder 2 doesn't bid.
     bidder2_worklet->InvokeGenerateBidCallback(/*bid=*/absl::nullopt);
 
@@ -9630,18 +9695,6 @@
     EXPECT_TRUE(result_.private_aggregation_event_map.empty());
     EXPECT_THAT(result_.interest_groups_that_bid,
                 testing::UnorderedElementsAre());
-
-    MetricsExpectations expectations(AuctionResult::kNoBids);
-    expectations.SetNumInterestGroups(2)
-        .SetNumOwnersAndDistinctOwners(2)
-        .SetNumSellers(1)
-        .SetNumBidderWorklets(2)
-        .SetNumInterestGroupsWithNoBids(1)
-        .SetNumInterestGroupsWithOnlyNonKAnonBid(1)
-        // We don't record negative latencies, so these metrics have no value.
-        .SetHasGenerateSingleBidLatencyMetrics(
-            !test_case.duration.is_negative());
-    CheckMetrics(expectations);
   }
 }
 
diff --git a/content/browser/interest_group/interest_group_auction.cc b/content/browser/interest_group/interest_group_auction.cc
index 57bb3e0..beebb59 100644
--- a/content/browser/interest_group/interest_group_auction.cc
+++ b/content/browser/interest_group/interest_group_auction.cc
@@ -158,10 +158,11 @@
       // they have the same url.
       return &ad;
     }
-    if (!ad.size_group || !ad_descriptor.size) {
-      // Since only one of the ads has a size specification, they are considered
-      // not matching.
-      continue;
+    if (!ad.size_group != !ad_descriptor.size) {
+      // When only one of the two ads has a size specification, they are
+      // considered matching. The caller is responsible for discarding the
+      // extraneous size information
+      return &ad;
     }
     // Both `blink::InterestGroup::Ad` and the ad from the bid have size
     // specifications. They are considered as matching ad only if their
@@ -720,6 +721,7 @@
       base::TimeDelta bidding_latency,
       auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
+      auction_worklet::mojom::RejectReason reject_reason,
       const std::vector<std::string>& errors) override {
     BidState* state = generate_bid_client_receiver_set_.current_context();
     const blink::InterestGroup& interest_group = state->bidder->interest_group;
@@ -739,7 +741,8 @@
         bidding_signals_data_version, has_bidding_signals_data_version,
         debug_loss_report_url, debug_win_report_url, set_priority,
         has_set_priority, std::move(update_priority_signals_overrides),
-        std::move(pa_requests), std::move(non_kanon_pa_requests), errors);
+        std::move(pa_requests), std::move(non_kanon_pa_requests), reject_reason,
+        errors);
   }
 
   // Closes all Mojo pipes, releases all weak pointers, and stops the timeout
@@ -1055,6 +1058,7 @@
         /*update_priority_signals_overrides=*/{},
         /*pa_requests=*/{},
         /*non_kanon_pa_requests=*/{},
+        /*reject_reason=*/auction_worklet::mojom::RejectReason::kNotAvailable,
         /*errors=*/{});
   }
 
@@ -1227,6 +1231,7 @@
           /*update_priority_signals_overrides=*/{},
           /*pa_requests=*/{},
           /*non_kanon_pa_requests=*/{},
+          /*reject_reason=*/auction_worklet::mojom::RejectReason::kNotAvailable,
           /*errors=*/{});
       // If this was the last bidder, and it was filtered out, there's nothing
       // else to do, and `this` may have already been deleted.
@@ -1322,6 +1327,7 @@
           update_priority_signals_overrides,
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
+      auction_worklet::mojom::RejectReason reject_reason,
       const std::vector<std::string>& errors) {
     DCHECK(!state->made_bid);
     DCHECK_GT(num_outstanding_bids_, 0);
@@ -1416,6 +1422,22 @@
     // Ignore invalid bids.
     std::unique_ptr<Bid> bid;
     std::string ad_metadata;
+
+    if (reject_reason != auction_worklet::mojom::RejectReason::kNotAvailable &&
+        reject_reason !=
+            auction_worklet::mojom::RejectReason::kWrongGenerateBidCurrency) {
+      // Only possible reject reason from generateBid(), besides the default
+      // "not available", is "wrong generate bid currency".
+      mojo_bid.reset();
+      mojo_kanon_bid.reset();
+      state->reject_reason =
+          auction_worklet::mojom::RejectReason::kNotAvailable;
+      generate_bid_client_receiver_set_.ReportBadMessage(
+          "Invalid bid reject_reason");
+    } else {
+      state->reject_reason = reject_reason;
+    }
+
     // `mojo_bid` is null if the worklet doesn't bid, or if the bidder worklet
     // fails to load / crashes.
     if (mojo_bid) {
@@ -1603,6 +1625,11 @@
           "Bid render ad must have a valid URL and size (if specified)");
       return nullptr;
     }
+    // If the matching ad does not have size specified, the bid to be created
+    // should not have size specified to fall back to old behavior.
+    blink::AdDescriptor ad_descriptor(
+        mojo_bid->ad_descriptor.url,
+        matching_ad->size_group ? mojo_bid->ad_descriptor.size : absl::nullopt);
 
     // Validate `ad_component` URLs, if present.
     std::vector<blink::AdDescriptor> ad_component_descriptors;
@@ -1626,15 +1653,21 @@
       // group's `ad_components` field.
       for (const blink::AdDescriptor& ad_component_descriptor :
            *mojo_bid->ad_component_descriptors) {
-        if (!FindMatchingAd(*interest_group.ad_components, bid_state.kanon_keys,
-                            interest_group, bid_role, /*is_component_ad=*/true,
-                            ad_component_descriptor)) {
+        const blink::InterestGroup::Ad* matching_ad_component = FindMatchingAd(
+            *interest_group.ad_components, bid_state.kanon_keys, interest_group,
+            bid_role, /*is_component_ad=*/true, ad_component_descriptor);
+        if (!matching_ad_component) {
           generate_bid_client_receiver_set_.ReportBadMessage(
               "Bid ad component must have a valid URL and size (if specified)");
           return nullptr;
         }
+        // If the matching ad does not have size specified, the bid to be
+        // created should not have size specified to fall back to old behavior.
+        ad_component_descriptors.emplace_back(ad_component_descriptor.url,
+                                              matching_ad_component->size_group
+                                                  ? ad_component_descriptor.size
+                                                  : absl::nullopt);
       }
-      ad_component_descriptors = *std::move(mojo_bid->ad_component_descriptors);
     }
 
     // Validate `debug_loss_report_url` and `debug_win_report_url`, if present.
@@ -1654,7 +1687,7 @@
     return std::make_unique<Bid>(
         bid_role, std::move(mojo_bid->ad), mojo_bid->bid,
         std::move(mojo_bid->bid_currency), mojo_bid->ad_cost,
-        std::move(mojo_bid->ad_descriptor), std::move(ad_component_descriptors),
+        std::move(ad_descriptor), std::move(ad_component_descriptors),
         std::move(mojo_bid->modeling_signals), mojo_bid->bid_duration,
         bidding_signals_data_version, matching_ad, &bid_state, auction_);
   }
@@ -2195,6 +2228,12 @@
     case auction_worklet::mojom::RejectReason::kBelowKAnonThreshold:
       reject_reason_str = "below-kanon-threshold";
       break;
+    case auction_worklet::mojom::RejectReason::kWrongGenerateBidCurrency:
+      reject_reason_str = "wrong-generate-bid-currency";
+      break;
+    case auction_worklet::mojom::RejectReason::kWrongScoreAdCurrency:
+      reject_reason_str = "wrong-score-ad-currency";
+      break;
   }
   return reject_reason_str;
 }
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 563a8818..d342d09 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -5896,23 +5896,23 @@
   url::Origin test_origin = url::Origin::Create(test_url);
   GURL ad_url = https_server_->GetURL("c.test", "/echo?render_cars");
 
-  EXPECT_EQ(
-      kSuccess,
-      JoinInterestGroupAndVerify(
-          blink::TestInterestGroupBuilder(
-              /*owner=*/test_origin,
-              /*name=*/"cars")
-              .SetBiddingUrl(https_server_->GetURL(
-                  "a.test", "/interest_group/bidding_logic_with_size.js"))
-              .SetAds(
-                  /*ads=*/{{{ad_url, /*metadata=*/absl::nullopt, "group_1"}}})
-              .SetAdSizes(
-                  {{{"size_1",
-                     blink::AdSize(100, blink::AdSize::LengthUnit::kScreenWidth,
-                                   50,
-                                   blink::AdSize::LengthUnit::kScreenHeight)}}})
-              .SetSizeGroups({{{"group_1", {"size_1"}}}})
-              .Build()));
+  EXPECT_EQ(kSuccess,
+            JoinInterestGroupAndVerify(
+                blink::TestInterestGroupBuilder(
+                    /*owner=*/test_origin,
+                    /*name=*/"cars")
+                    .SetBiddingUrl(https_server_->GetURL(
+                        "a.test", "/interest_group/bidding_logic_with_size.js"))
+                    .SetAds(
+                        /*ads=*/{{{ad_url, /*metadata=*/absl::nullopt,
+                                   /*size_group=*/"group_1"}}})
+                    .SetAdSizes(
+                        {{{"size_1",
+                           blink::AdSize(
+                               100, blink::AdSize::LengthUnit::kScreenWidth, 50,
+                               blink::AdSize::LengthUnit::kScreenHeight)}}})
+                    .SetSizeGroups({{{"group_1", {"size_1"}}}})
+                    .Build()));
 
   std::string auction_config = JsReplace(
       R"({
@@ -5925,6 +5925,88 @@
   RunAuctionAndWaitForURLAndNavigateIframe(auction_config, ad_url);
 }
 
+// Runs an auction just like InterestGroupBrowserTest.RunAdAuctionWithWinner,
+// but where the render size is specified only in the interest group, not the
+// bid. When size is only specified in one place, the ads are considered
+// matching (as long as their urls are the same), but the auction proceeds as if
+// no size information was specified.
+IN_PROC_BROWSER_TEST_F(
+    InterestGroupBrowserTest,
+    RunAdAuctionWithSizeForInterestGroupWithWinnerNoMacroSubstitution) {
+  GURL test_url = https_server_->GetURL("a.test", "/page_with_iframe.html");
+  ASSERT_TRUE(NavigateToURL(shell(), test_url));
+  url::Origin test_origin = url::Origin::Create(test_url);
+  GURL ad_url = https_server_->GetURL(
+      "c.test", "/echo?render_cars&size={%AD_WIDTH%}x{%AD_HEIGHT%}");
+
+  EXPECT_EQ(kSuccess,
+            JoinInterestGroupAndVerify(
+                blink::TestInterestGroupBuilder(
+                    /*owner=*/test_origin,
+                    /*name=*/"cars")
+                    .SetBiddingUrl(https_server_->GetURL(
+                        "a.test", "/interest_group/bidding_logic.js"))
+                    .SetAds(
+                        /*ads=*/{{{ad_url, /*metadata=*/absl::nullopt,
+                                   /*size_group=*/"group_1"}}})
+                    .SetAdSizes(
+                        {{{"size_1",
+                           blink::AdSize(
+                               100, blink::AdSize::LengthUnit::kScreenWidth, 50,
+                               blink::AdSize::LengthUnit::kScreenHeight)}}})
+                    .SetSizeGroups({{{"group_1", {"size_1"}}}})
+                    .Build()));
+
+  std::string auction_config = JsReplace(
+      R"({
+          seller: $1,
+          decisionLogicUrl: $2,
+          interestGroupBuyers: [$1]
+        })",
+      test_origin,
+      https_server_->GetURL("a.test", "/interest_group/decision_logic.js"));
+  // Size is only specified in the interest group, ad size macro substitution
+  // should not happen.
+  RunAuctionAndWaitForURLAndNavigateIframe(auction_config, ad_url);
+}
+
+// Runs an auction just like InterestGroupBrowserTest.RunAdAuctionWithWinner,
+// but where the render size is specified only in the bid, not the interest
+// group. When size is only specified in one place, the ads are considered
+// matching (as long as their urls are the same), but the auction proceeds as if
+// no size information was specified.
+IN_PROC_BROWSER_TEST_F(
+    InterestGroupBrowserTest,
+    RunAdAuctionWithSizeForBidWithWinnerNoMacroSubstitution) {
+  GURL test_url = https_server_->GetURL("a.test", "/page_with_iframe.html");
+  ASSERT_TRUE(NavigateToURL(shell(), test_url));
+  url::Origin test_origin = url::Origin::Create(test_url);
+  GURL ad_url = https_server_->GetURL(
+      "c.test", "/echo?render_cars&size={%AD_WIDTH%}x{%AD_HEIGHT%}");
+
+  EXPECT_EQ(kSuccess,
+            JoinInterestGroupAndVerify(
+                blink::TestInterestGroupBuilder(
+                    /*owner=*/test_origin,
+                    /*name=*/"cars")
+                    .SetBiddingUrl(https_server_->GetURL(
+                        "a.test", "/interest_group/bidding_logic_with_size.js"))
+                    .SetAds(/*ads=*/{{{ad_url, /*metadata=*/absl::nullopt}}})
+                    .Build()));
+
+  std::string auction_config = JsReplace(
+      R"({
+          seller: $1,
+          decisionLogicUrl: $2,
+          interestGroupBuyers: [$1]
+        })",
+      test_origin,
+      https_server_->GetURL("a.test", "/interest_group/decision_logic.js"));
+  // Size is only specified in the bid, ad size macro substitution should not
+  // happen.
+  RunAuctionAndWaitForURLAndNavigateIframe(auction_config, ad_url);
+}
+
 // Runs auction just like test InterestGroupBrowserTest.RunAdAuctionWithWinner,
 // but runs with the ads specified with sizes info. The ad url contains size
 // macros, which should be substituted with the size from the winning bid.
diff --git a/content/browser/interest_group/mock_auction_process_manager.cc b/content/browser/interest_group/mock_auction_process_manager.cc
index 14bf6b61..c0108c5 100644
--- a/content/browser/interest_group/mock_auction_process_manager.cc
+++ b/content/browser/interest_group/mock_auction_process_manager.cc
@@ -206,7 +206,8 @@
     std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>
         pa_requests,
     auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
-        dependency_latencies) {
+        dependency_latencies,
+    auction_worklet::mojom::RejectReason reject_reason) {
   WaitForGenerateBid();
 
   base::RunLoop run_loop;
@@ -241,6 +242,7 @@
         /*non_kanon_pa_requests=*/{},
         /*bidding_latency=*/bidding_latency_,
         /*generate_bid_dependency_latencies=*/std::move(dependency_latencies),
+        reject_reason,
         /*errors=*/std::vector<std::string>());
     return;
   }
@@ -263,6 +265,7 @@
       /*non_kanon_pa_requests=*/{},
       /*bidding_latency=*/bidding_latency_,
       /*generate_bid_dependency_latencies=*/std::move(dependency_latencies),
+      reject_reason,
       /*errors=*/std::vector<std::string>());
 }
 
diff --git a/content/browser/interest_group/mock_auction_process_manager.h b/content/browser/interest_group/mock_auction_process_manager.h
index b82daae..1c12c7b 100644
--- a/content/browser/interest_group/mock_auction_process_manager.h
+++ b/content/browser/interest_group/mock_auction_process_manager.h
@@ -141,7 +141,9 @@
           pa_requests = {},
       auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
           dependency_latencies =
-              auction_worklet::mojom::GenerateBidDependencyLatenciesPtr());
+              auction_worklet::mojom::GenerateBidDependencyLatenciesPtr(),
+      auction_worklet::mojom::RejectReason reject_reason =
+          auction_worklet::mojom::RejectReason::kNotAvailable);
 
   // Waits for ReportWin() to be invoked.
   void WaitForReportWin();
diff --git a/content/browser/loader/cross_site_document_blocking_browsertest.cc b/content/browser/loader/cross_site_document_blocking_browsertest.cc
index c8c34ad..4b9fedf 100644
--- a/content/browser/loader/cross_site_document_blocking_browsertest.cc
+++ b/content/browser/loader/cross_site_document_blocking_browsertest.cc
@@ -85,98 +85,6 @@
   return CorbExpectations(expectations | kBlockResourcesAsError);
 }
 
-std::ostream& operator<<(std::ostream& os, const CorbExpectations& value) {
-  if (value == 0) {
-    os << "(none)";
-    return os;
-  }
-
-  os << "( ";
-  if (0 != (value & kShouldBeBlocked))
-    os << "kShouldBeBlocked ";
-  if (0 != (value & kShouldBeSniffed))
-    os << "kShouldBeSniffed ";
-  if (0 != (value & kBlockResourcesAsError))
-    os << "kBlockResourcesAsError ";
-  os << ")";
-  return os;
-}
-
-// Ensure the correct histograms are incremented for blocking events.
-// Assumes the resource type is XHR.
-void InspectHistograms(const base::HistogramTester& histograms,
-                       const CorbExpectations& expectations,
-                       const std::string& resource_name) {
-  FetchHistogramsFromChildProcesses();
-
-  // No ORB-specific UMA at this point.
-  if (base::FeatureList::IsEnabled(
-          network::features::kOpaqueResponseBlockingV01_LAUNCHED)) {
-    return;
-  }
-
-  CorbMimeType expected_mime_type = CorbMimeType::kInvalidMimeType;
-  if (base::MatchPattern(resource_name, "*.html")) {
-    expected_mime_type = CorbMimeType::kHtml;
-  } else if (base::MatchPattern(resource_name, "*.xml")) {
-    expected_mime_type = CorbMimeType::kXml;
-  } else if (base::MatchPattern(resource_name, "*.json")) {
-    expected_mime_type = CorbMimeType::kJson;
-  } else if (base::MatchPattern(resource_name, "*.txt")) {
-    expected_mime_type = CorbMimeType::kPlain;
-  } else if (base::MatchPattern(resource_name, "*.zip") ||
-             base::MatchPattern(resource_name, "*.pdf")) {
-    expected_mime_type = CorbMimeType::kNeverSniffed;
-  } else {
-    expected_mime_type = CorbMimeType::kOthers;
-  }
-
-  // Determine the appropriate histograms, including a start and end action
-  // (which are verified in unit tests), a read size if it was sniffed, and
-  // additional blocked metrics if it was blocked.
-  base::HistogramTester::CountsMap expected_counts;
-  std::string base = "SiteIsolation.XSD.Browser";
-  expected_counts[base + ".Action"] = 2;
-  if (0 != (expectations & kShouldBeBlocked)) {
-    expected_counts[base + ".Blocked.CanonicalMimeType"] = 1;
-  }
-
-  // Make sure that the expected metrics, and only those metrics, were
-  // incremented.
-  EXPECT_THAT(histograms.GetTotalCountsForPrefix("SiteIsolation.XSD.Browser"),
-              testing::ContainerEq(expected_counts))
-      << "For resource_name=" << resource_name
-      << ", expectations=" << expectations;
-
-  // Determine if the bucket for the resource type (XHR) was incremented.
-  if (0 != (expectations & kShouldBeBlocked)) {
-    EXPECT_THAT(histograms.GetAllSamples(base + ".Blocked.CanonicalMimeType"),
-                testing::ElementsAre(
-                    base::Bucket(static_cast<int>(expected_mime_type), 1)))
-        << "The wrong CorbMimeType bucket was incremented.";
-  }
-
-  // SiteIsolation.XSD.Browser.Action should always include kResponseStarted.
-  histograms.ExpectBucketCount(base + ".Action",
-                               static_cast<int>(Action::kResponseStarted), 1);
-
-  // Second value in SiteIsolation.XSD.Browser.Action depends on |expectations|.
-  Action expected_action = static_cast<Action>(-1);
-  if (expectations & kShouldBeBlocked) {
-    if (expectations & kShouldBeSniffed)
-      expected_action = Action::kBlockedAfterSniffing;
-    else
-      expected_action = Action::kBlockedWithoutSniffing;
-  } else {
-    if (expectations & kShouldBeSniffed)
-      expected_action = Action::kAllowedAfterSniffing;
-    else
-      expected_action = Action::kAllowedWithoutSniffing;
-  }
-  histograms.ExpectBucketCount(base + ".Action",
-                               static_cast<int>(expected_action), 1);
-}
-
 // Gets contents of a file at //content/test/data/<dir>/<file>.
 std::string GetTestFileContents(const char* dir, const char* file) {
   base::ScopedAllowBlockingForTesting allow_io;
@@ -492,8 +400,6 @@
 };
 
 enum class TestMode {
-  kWithCORBProtectionSniffing,
-  kWithoutCORBProtectionSniffing,
   kWithORBv01,
   kWithORBv02,
 };
@@ -508,34 +414,16 @@
  public:
   CrossSiteDocumentBlockingImgElementTest() {
     switch (GetParam().mode) {
-      case TestMode::kWithCORBProtectionSniffing:
-        scoped_feature_list_.InitWithFeatures(
-            /* enabled_features= */ {network::features::
-                                         kCORBProtectionSniffing},
-            /* disabled_features= */ {
-                network::features::kOpaqueResponseBlockingV01_LAUNCHED,
-                network::features::kOpaqueResponseBlockingV02});
-        break;
-      case TestMode::kWithoutCORBProtectionSniffing:
-        scoped_feature_list_.InitWithFeatures(
-            /* enabled_features= */ {},
-            /* disabled_features= */ {
-                network::features::kCORBProtectionSniffing,
-                network::features::kOpaqueResponseBlockingV01_LAUNCHED,
-                network::features::kOpaqueResponseBlockingV02});
-        break;
       case TestMode::kWithORBv01:
         scoped_feature_list_.InitWithFeatures(
-            /* enabled_features= */ {network::features::
-                                         kOpaqueResponseBlockingV01_LAUNCHED},
+            /* enabled_features= */ {},
             /* disabled_features= */ {
                 network::features::kOpaqueResponseBlockingV02});
         break;
       case TestMode::kWithORBv02:
         scoped_feature_list_.InitWithFeatures(
             /* enabled_features= */
-            {network::features::kOpaqueResponseBlockingV01_LAUNCHED,
-             network::features::kOpaqueResponseBlockingV02},
+            {network::features::kOpaqueResponseBlockingV02},
             /* disabled_features= */ {});
         break;
     }
@@ -580,25 +468,8 @@
     interceptor.WaitForRequestCompletion();
 
     // Verify...
-    InspectHistograms(histograms, expectations, resource);
     interceptor.Verify(expectations,
                        GetTestFileContents("site_isolation", resource.c_str()));
-
-    // Verify ORB-specific histograms...
-    using BlockingDecisionReason =
-        network::corb::OpaqueResponseBlockingAnalyzer::BlockingDecisionReason;
-    if (resource == "html.octet-stream") {
-      histograms.ExpectUniqueSample(
-          "SiteIsolation.ORB.CorbVsOrb.OrbBlockedAndCorbDidnt.Reason",
-          BlockingDecisionReason::kSniffedAsHtml, 1);
-    } else if (resource == "xml.octet-stream") {
-      histograms.ExpectUniqueSample(
-          "SiteIsolation.ORB.CorbVsOrb.OrbBlockedAndCorbDidnt.Reason",
-          BlockingDecisionReason::kSniffedAsXml, 1);
-    } else {
-      histograms.ExpectTotalCount(
-          "SiteIsolation.ORB.CorbVsOrb.OrbBlockedAndCorbDidnt.Reason", 0);
-    }
   }
 
  private:
@@ -615,45 +486,16 @@
   VerifyImgRequest(resource, expectations);
 }
 
-#define IMG_TEST(tag, resource, expectations)                                  \
-  INSTANTIATE_TEST_SUITE_P(                                                    \
-      ProtectionSniffingOn_##tag, CrossSiteDocumentBlockingImgElementTest,     \
-      ::testing::Values(ImgTestParams{                                         \
-          resource, expectations, TestMode::kWithoutCORBProtectionSniffing})); \
-  INSTANTIATE_TEST_SUITE_P(                                                    \
-      ProtectionSniffingOff_##tag, CrossSiteDocumentBlockingImgElementTest,    \
-      ::testing::Values(ImgTestParams{                                         \
-          resource, expectations, TestMode::kWithCORBProtectionSniffing}));    \
-  INSTANTIATE_TEST_SUITE_P(                                                    \
-      ORBv01_##tag, CrossSiteDocumentBlockingImgElementTest,                   \
-      ::testing::Values(                                                       \
-          ImgTestParams{resource, expectations, TestMode::kWithORBv01}));      \
-  INSTANTIATE_TEST_SUITE_P(                                                    \
-      ORBv02_##tag, CrossSiteDocumentBlockingImgElementTest,                   \
-      ::testing::Values(ImgTestParams{resource, BlockAsError(expectations),    \
+#define IMG_TEST(tag, resource, expectations)                               \
+  INSTANTIATE_TEST_SUITE_P(                                                 \
+      ORBv01_##tag, CrossSiteDocumentBlockingImgElementTest,                \
+      ::testing::Values(                                                    \
+          ImgTestParams{resource, expectations, TestMode::kWithORBv01}));   \
+  INSTANTIATE_TEST_SUITE_P(                                                 \
+      ORBv02_##tag, CrossSiteDocumentBlockingImgElementTest,                \
+      ::testing::Values(ImgTestParams{resource, BlockAsError(expectations), \
                                       TestMode::kWithORBv02}));
 
-#define IMG_TEST_WITH_DIFFERENT_ORB_EXPECTATIONS(                             \
-    tag, resource, corb_expectations, orb_expectations)                       \
-  INSTANTIATE_TEST_SUITE_P(ProtectionSniffingOn_##tag,                        \
-                           CrossSiteDocumentBlockingImgElementTest,           \
-                           ::testing::Values(ImgTestParams{                   \
-                               resource, corb_expectations,                   \
-                               TestMode::kWithoutCORBProtectionSniffing}));   \
-  INSTANTIATE_TEST_SUITE_P(ProtectionSniffingOff_##tag,                       \
-                           CrossSiteDocumentBlockingImgElementTest,           \
-                           ::testing::Values(ImgTestParams{                   \
-                               resource, corb_expectations,                   \
-                               TestMode::kWithCORBProtectionSniffing}));      \
-  INSTANTIATE_TEST_SUITE_P(                                                   \
-      ORBv01_##tag, CrossSiteDocumentBlockingImgElementTest,                  \
-      ::testing::Values(                                                      \
-          ImgTestParams{resource, orb_expectations, TestMode::kWithORBv01})); \
-  INSTANTIATE_TEST_SUITE_P(                                                   \
-      ORBv02_##tag, CrossSiteDocumentBlockingImgElementTest,                  \
-      ::testing::Values(ImgTestParams{                                        \
-          resource, BlockAsError(orb_expectations), TestMode::kWithORBv02}));
-
 // The following are files under content/test/data/site_isolation. All
 // should be disallowed for cross site XHR under the document blocking policy.
 //   valid.*        - Correctly labeled HTML/XML/JSON files.
@@ -682,18 +524,10 @@
          "nosniff.json-prefixed.js",
          kShouldBeSniffedAndBlocked)
 
-// ORB provides some incremental security benefits over CORB.  For example, CORB
-// allows responses that 1) have an unrecognized MIME type and 2) don't sniff as
-// JSON.  ORB blocks responses with non-image/audio/video MIME type that don't
+// ORB blocks responses with non-image/audio/video MIME type that don't
 // sniff as Javascript (ORBv0.1 blocks ones that sniff as HTML or XML or JSON).
-IMG_TEST_WITH_DIFFERENT_ORB_EXPECTATIONS(html_octet_stream,
-                                         "html.octet-stream",
-                                         /* CORB */ kShouldBeSniffedAndAllowed,
-                                         /* ORB */ kShouldBeSniffedAndBlocked)
-IMG_TEST_WITH_DIFFERENT_ORB_EXPECTATIONS(xml_octet_stream,
-                                         "xml.octet-stream",
-                                         /* CORB */ kShouldBeSniffedAndAllowed,
-                                         /* ORB */ kShouldBeSniffedAndBlocked)
+IMG_TEST(html_octet_stream, "html.octet-stream", kShouldBeSniffedAndBlocked)
+IMG_TEST(xml_octet_stream, "xml.octet-stream", kShouldBeSniffedAndBlocked)
 
 // ORB detects audio/video responses before the final Javascript sniffing (or in
 // the case of ORBv0.1, confirmation sniffing for HTML/XML/JSON).  This
@@ -702,10 +536,7 @@
 // OTOH, if ORB detects some audio/video responses based on the MIME type of the
 // response, then it may allow some cases that CORB would block - such as a JSON
 // response incorrectly labeled as "audio/x-wav".
-IMG_TEST_WITH_DIFFERENT_ORB_EXPECTATIONS(json_wav,
-                                         "json.wav",
-                                         /* CORB */ kShouldBeSniffedAndBlocked,
-                                         /* ORB */ kShouldBeSniffedAndAllowed)
+IMG_TEST(json_wav, "json.wav", kShouldBeSniffedAndAllowed)
 
 // These files should be disallowed without sniffing.
 //   nosniff.*   - Won't sniff correctly, but blocked because of nosniff.
@@ -762,26 +593,16 @@
  public:
   CrossSiteDocumentBlockingTest() {
     switch (GetParam()) {
-      case TestMode::kWithCORBProtectionSniffing:
-        scoped_feature_list_.InitAndEnableFeature(
-            network::features::kCORBProtectionSniffing);
-        break;
-      case TestMode::kWithoutCORBProtectionSniffing:
-        scoped_feature_list_.InitAndDisableFeature(
-            network::features::kCORBProtectionSniffing);
-        break;
       case TestMode::kWithORBv01:
         scoped_feature_list_.InitWithFeatures(
-            /* enabled_features= */ {network::features::
-                                         kOpaqueResponseBlockingV01_LAUNCHED},
+            /* enabled_features= */ {},
             /* disabled_features= */ {
                 network::features::kOpaqueResponseBlockingV02});
         break;
       case TestMode::kWithORBv02:
         scoped_feature_list_.InitWithFeatures(
             /* enabled_features= */
-            {network::features::kOpaqueResponseBlockingV01_LAUNCHED,
-             network::features::kOpaqueResponseBlockingV02},
+            {network::features::kOpaqueResponseBlockingV02},
             /* disabled_features= */ {});
         break;
     }
@@ -861,7 +682,6 @@
     // Fetch and verify results of the fetch.
     EXPECT_EQ(false, EvalJs(shell(), base::StringPrintf("sendRequest('%s');",
                                                         resource)));
-    InspectHistograms(histograms, kShouldBeAllowedWithoutSniffing, resource);
   }
 }
 
@@ -904,8 +724,6 @@
 
   // Verify that the response was not blocked.
   EXPECT_EQ("runMe({ \"name\" : \"chromium\" });", fetch_result);
-  InspectHistograms(histograms, kShouldBeAllowedWithoutSniffing,
-                    "nosniff.html");
 }
 
 // Regression test for https://crbug.com/958421.
@@ -914,7 +732,6 @@
 
   // Prepare to verify results of a fetch.
   GURL resource_url("http://foo.com/title2.html");
-  const char* resource = "title2.html";
   const char kFetchScriptTemplate[] = R"(
       fetch($1, {mode: 'no-cors'}).then(response => 'ok');
   )";
@@ -954,7 +771,6 @@
     FetchHistogramsFromChildProcesses();
     base::HistogramTester histograms;
     ASSERT_EQ("ok", EvalJs(popup, fetch_script));
-    InspectHistograms(histograms, kShouldBeAllowedWithoutSniffing, resource);
   }
 
   // Navigate the popup and then go back to the 'about:blank' URL.
@@ -977,7 +793,6 @@
     FetchHistogramsFromChildProcesses();
     base::HistogramTester histograms;
     ASSERT_EQ("ok", EvalJs(popup, fetch_script));
-    InspectHistograms(histograms, kShouldBeAllowedWithoutSniffing, resource);
   }
 }
 
@@ -1293,11 +1108,6 @@
       });
   )";
   EXPECT_EQ("FETCH SUCCEEDED", EvalJs(shell(), kFetchWait));
-
-  // Verify that the response completed successfully, was blocked and was logged
-  // as having initially a non-empty body.
-  InspectHistograms(histograms, kShouldBeBlockedWithoutSniffing,
-                    "nosniff.json");
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
@@ -1373,7 +1183,6 @@
       });
   )";
   EXPECT_EQ(123, EvalJs(shell()->web_contents(), wait_script));
-  InspectHistograms(histograms, kShouldBeBlockedWithoutSniffing, "x.html");
 
   // Finish the HTTP response - this should store the response in the cache.
   response.Done();
@@ -1404,16 +1213,6 @@
             EvalJs(ChildFrameAt(shell()->web_contents(), 0), fetch_script));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    WithCORBProtectionSniffing,
-    CrossSiteDocumentBlockingTest,
-    ::testing::Values(TestMode::kWithCORBProtectionSniffing));
-
-INSTANTIATE_TEST_SUITE_P(
-    WithoutCORBProtectionSniffing,
-    CrossSiteDocumentBlockingTest,
-    ::testing::Values(TestMode::kWithoutCORBProtectionSniffing));
-
 INSTANTIATE_TEST_SUITE_P(WithORBv01,
                          CrossSiteDocumentBlockingTest,
                          ::testing::Values(TestMode::kWithORBv01));
@@ -1535,10 +1334,6 @@
   base::HistogramTester histograms;
   std::string response = EvalJs(shell(), script).ExtractString();
 
-  // Verify that CORB blocked the response from the network (from
-  // |cross_origin_https_server_|) to the service worker.
-  InspectHistograms(histograms, kShouldBeBlockedWithoutSniffing, "network.txt");
-
   // Verify that the service worker replied with an expected error.
   // Replying with an error means that CORB is only active once (for the
   // initial, real network request) and therefore the test doesn't get
diff --git a/content/browser/loader/navigation_early_hints_manager.h b/content/browser/loader/navigation_early_hints_manager.h
index 3502245b..34e8e1a 100644
--- a/content/browser/loader/navigation_early_hints_manager.h
+++ b/content/browser/loader/navigation_early_hints_manager.h
@@ -8,6 +8,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "content/common/content_export.h"
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h
index 0922804..731783a 100644
--- a/content/browser/loader/navigation_url_loader_impl.h
+++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_LOADER_NAVIGATION_URL_LOADER_IMPL_H_
 #define CONTENT_BROWSER_LOADER_NAVIGATION_URL_LOADER_IMPL_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc
index 3bb6d5d..e6fae64 100644
--- a/content/browser/media/capture/desktop_capture_device.cc
+++ b/content/browser/media/capture/desktop_capture_device.cc
@@ -72,6 +72,10 @@
 // (EMA) filter used to calculate the current frame rate (in frames per second).
 constexpr float kAlpha = 0.1;
 
+// The maximum refresh delay. Callers of the API that request a refresh
+// frame may end up waiting up to this long.
+static constexpr base::TimeDelta kMaxRefreshDelay = base::Seconds(1);
+
 webrtc::DesktopRect ComputeLetterboxRect(
     const webrtc::DesktopSize& max_size,
     const webrtc::DesktopSize& source_size) {
@@ -165,8 +169,12 @@
   // Implementation of VideoCaptureDevice methods.
   void AllocateAndStart(const media::VideoCaptureParams& params,
                         std::unique_ptr<Client> client);
+  // Executes a refresh capture, if conditions permit. Otherwise, schedules a
+  // later retry. If a refresh was already pending, a new request is ignored.
   void RequestRefreshFrame();
 
+  base::TimeDelta GetDelayBeforeNextRefreshAttempt() const;
+
   void SetNotificationWindowId(gfx::NativeViewId window_id);
 
   void SetMockTimeForTesting(
@@ -182,17 +190,23 @@
     webrtc::DesktopCapturer::Result result,
     std::unique_ptr<webrtc::DesktopFrame> frame) override;
 
-  // Sends the last received frame (stored in |output_frame_|) to the client
-  // using the colorspace in |last_frame_color_space_|.
-  // Does not schedule the next frame.
-  void SendLastReceivedFrameToClient(bool is_refresh_frame);
-
   // Method that is scheduled on |task_runner_| to be called on regular interval
   // to capture a frame.
   void OnCaptureTimer();
 
-  // Captures a frame. Upon completion, schedules the next frame.
-  void CaptureFrame();
+  // Method that is scheduled on |task_runner_| and called as a result of a
+  // previous call to |RequestRefreshFrame|. Leads to capturing of a new frame
+  // when the default captured stream did not provide frames on time for some
+  // reason. No-op if a default capture event is already pending by the time
+  // this method is called.
+  void OnRequestRefreshFrameTimer();
+
+  // Captures a frame. Upon completion, schedules the next frame. The frame type
+  // is a refresh frame if `is_refresh_frame` is true and a default frame
+  // otherwise. Sending refresh frames is expected to be a rare event since a
+  // refresh request will be canceled by default capture events and they are
+  // periodic.
+  void CaptureFrame(bool is_refresh_frame);
 
   // Schedules a timer for the next call to |CaptureFrame|. This method assumes
   // that |CaptureFrame| has already been called at least once before.
@@ -231,9 +245,12 @@
   // Size of frame most recently captured from the source.
   webrtc::DesktopSize last_frame_size_;
 
-  // DesktopFrame into which captured frames are stored; either intact or
-  // possibly after being down-scaled and/or letterboxed, depending upon the
-  // caller's requested capture capabilities. The output frame is black when
+  // DesktopFrame into which captured frames are down-scaled and/or letterboxed,
+  // depending upon the caller's requested capture capabilities. If frames can
+  // be returned to the caller directly then this is NULL.
+  // TODO(https://crbug.com/1444340): should NOT be used to store frames
+  // received from the underlying capturer since it can cause cursor flickering
+  // if the frame is a DesktopFrameWithCursor. The output frame is black when
   // |output_frame_is_black_| is set. This can happen when a minimized window
   // is shared.
   std::unique_ptr<webrtc::DesktopFrame> output_frame_;
@@ -243,9 +260,6 @@
   // we are only sending black frames.
   bool output_frame_is_black_ = false;
 
-  // Copy of the colorspace used for the most recent frame sent to the client.
-  gfx::ColorSpace output_frame_color_space_;
-
   // Determines the size of frames to deliver to the |client_|.
   media::CaptureResolutionChooser resolution_chooser_;
 
@@ -254,12 +268,22 @@
   // Timer used to capture the frame.
   std::unique_ptr<base::OneShotTimer> capture_timer_;
 
+  // This timer is started whenever the consumer needs another frame delivered.
+  // This might be because: 1) the consumer was just started and needs an
+  // initial frame; 2) the capture target changed; 3) to satisfy explicit
+  // requests for a refresh frame, when RequestRefreshFrame() has been called.
+  std::unique_ptr<base::OneShotTimer> request_refresh_frame_timer_;
+
   // See above description of kDefaultMaximumCpuConsumptionPercentage.
   int max_cpu_consumption_percentage_;
 
   // True when waiting for |desktop_capturer_| to capture current frame.
   bool capture_in_progress_;
 
+  // True when waiting for |desktop_capturer_| to capture current frame as a
+  // response to refresh frame request.
+  bool refresh_in_progress_;
+
   // True if the first capture call has returned. Used to log the first capture
   // result.
   bool first_capture_returned_;
@@ -285,6 +309,8 @@
   // which results in an average capture frame rate in `frame_rate_`.
   base::TimeTicks last_capture_time_;
 
+  std::unique_ptr<webrtc::BasicDesktopFrame> black_frame_;
+
   // TODO(jiayl): Remove wake_lock_ when there is an API to keep the
   // screen from sleeping for the drive-by web.
   mojo::Remote<device::mojom::WakeLock> wake_lock_;
@@ -300,6 +326,7 @@
     : task_runner_(task_runner),
       desktop_capturer_(std::move(capturer)),
       capture_timer_(new base::OneShotTimer()),
+      request_refresh_frame_timer_(new base::OneShotTimer()),
       max_cpu_consumption_percentage_(GetMaximumCpuConsumptionPercentage()),
       capture_in_progress_(false),
       first_capture_returned_(false),
@@ -310,6 +337,8 @@
 DesktopCaptureDevice::Core::~Core() {
   DCHECK(task_runner_->BelongsToCurrentThread());
   client_.reset();
+  output_frame_.reset();
+  last_frame_size_.set(0, 0);
   desktop_capturer_.reset();
 }
 
@@ -337,7 +366,7 @@
   resolution_chooser_.SetConstraints(constraints.min_frame_size,
                                      constraints.max_frame_size,
                                      constraints.fixed_aspect_ratio);
-  VLOG(2) << __func__ << " (requested_frame_rate=" << requested_frame_rate_
+  VLOG(1) << __func__ << " (requested_frame_rate=" << requested_frame_rate_
           << ", max_frame_size=" << constraints.max_frame_size.ToString()
           << ", requested_frame_duration="
           << requested_frame_duration_.InMilliseconds()
@@ -351,18 +380,37 @@
   // Assume it will be always started successfully for now.
   client_->OnStarted();
 
-  CaptureFrame();
+  CaptureFrame(/*is_refresh_frame=*/false);
 }
 
 void DesktopCaptureDevice::Core::RequestRefreshFrame() {
   DCHECK(task_runner_->BelongsToCurrentThread());
   TRACE_EVENT0("webrtc", __func__);
-  VLOG(2) << __func__ << " is called by the client";
-  // Simply send the last received frame, if we ever received one. Don't
-  // schedule a new frame.
-  if (output_frame_) {
-    SendLastReceivedFrameToClient(/*is_refresh_frame=*/true);
+  VLOG(2) << __func__;
+
+  if (!client_) {
+    return;
   }
+
+  // Ignore this request if one is already pending.
+  if (request_refresh_frame_timer_->IsRunning()) {
+    return;
+  }
+
+  // Schedule a new attempt to capture a refresh frame within ~2 times the
+  // specified frame duration. The attempt will be canceled if a default capture
+  // event is triggered before this timer fires.
+  request_refresh_frame_timer_->Start(FROM_HERE,
+                                      GetDelayBeforeNextRefreshAttempt(), this,
+                                      &Core::OnRequestRefreshFrameTimer);
+}
+
+base::TimeDelta DesktopCaptureDevice::Core::GetDelayBeforeNextRefreshAttempt()
+    const {
+  // The delay should be long enough to prevent interrupting the smooth timing
+  // of captured frames. However, the delay should not be excessively long
+  // either. Two default frame periods should be "just right."
+  return std::min(kMaxRefreshDelay, 2 * requested_frame_duration_);
 }
 
 void DesktopCaptureDevice::Core::SetNotificationWindowId(
@@ -385,9 +433,10 @@
     std::unique_ptr<webrtc::DesktopFrame> frame) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(client_);
-  DCHECK(capture_in_progress_);
-  TRACE_EVENT0("webrtc", __func__);
+  DCHECK(capture_in_progress_ || refresh_in_progress_);
   capture_in_progress_ = false;
+  const bool frame_is_refresh = refresh_in_progress_;
+  refresh_in_progress_ = false;
 
   bool success = result == webrtc::DesktopCapturer::Result::SUCCESS;
 
@@ -427,13 +476,15 @@
   // Continue capturing frames when there are no changes in updated regions
   // since the last captured frame but don't send the same frame again to the
   // client. Clients may call RequestRefreshFrame() to ask for a copy of the
-  // last captured frame. Check |output_frame_| to ensure that at least one
-  // valid frame has already been captured.
+  // last captured frame. Checking `first_ref_time_` ensures that at least one
+  // frame has been captured before 0Hz can be activated. The zero-hertz mode
+  // is disabled if the captured frame is a refresh frame to guarantee that the
+  // client actually receives a new frame when explicitly asking for it.
   // |zero_hertz_is_supported()| can be false in combination with capturers that
   // do not support the 0Hz mode, e.g. Windows capturers using the WGC API.
-  const bool zero_hertz_is_active = zero_hertz_is_supported() &&
-                                    output_frame_ &&
-                                    frame->updated_region().is_empty();
+  const bool zero_hertz_is_active =
+      zero_hertz_is_supported() && !first_ref_time_.is_null() &&
+      !frame_is_refresh && frame->updated_region().is_empty();
   VLOG(2) << __func__ << " [SUCCESS]" << (zero_hertz_is_active ? "[0Hz]" : "");
   if (zero_hertz_is_supported()) {
     LogDesktopCaptureZeroHzIsActive(capturer_type_, zero_hertz_is_active);
@@ -450,8 +501,6 @@
     resolution_chooser_.SetSourceSize(
         gfx::Size(frame->size().width(), frame->size().height()));
     last_frame_size_ = frame->size();
-    VLOG(2) << "  last_frame_size=(" << last_frame_size_.width() << "x"
-            << last_frame_size_.height() << ")";
   }
   // Align to 2x2 pixel boundaries, as required by OnIncomingCapturedData() so
   // it can convert the frame to I420 format.
@@ -463,16 +512,12 @@
     // be guaranteed.
     output_size.set(2, 2);
   }
-  VLOG(2) << "  output_size=(" << output_size.width() << "x"
-          << output_size.height() << ")";
+  VLOG(2) << __func__ << " [output_size=(" << output_size.width() << "x"
+          << output_size.height() << ")]";
 
-  gfx::ColorSpace frame_color_space;
-  if (!frame->icc_profile().empty()) {
-    gfx::ICCProfile icc_profile = gfx::ICCProfile::FromData(
-        frame->icc_profile().data(), frame->icc_profile().size());
-    frame_color_space = icc_profile.GetColorSpace();
-  }
-  output_frame_color_space_ = frame_color_space;
+  size_t output_bytes = output_size.width() * output_size.height() *
+                        webrtc::DesktopFrame::kBytesPerPixel;
+  const uint8_t* output_data = nullptr;
 
   if (frame->size().width() <= 1 || frame->size().height() <= 1) {
     // On OSX We receive a 1x1 frame when the shared window is minimized. It
@@ -485,9 +530,7 @@
       output_frame_is_black_ = true;
     }
     if (!output_frame_is_black_) {
-      size_t total_bytes = webrtc::DesktopFrame::kBytesPerPixel *
-                           output_size.width() * output_size.height();
-      memset(output_frame_->data(), 0, total_bytes);
+      output_frame_->SetFrameDataToBlack();
       output_frame_is_black_ = true;
     }
   } else {
@@ -534,9 +577,9 @@
                         frame->size().height(), output_rect_data,
                         output_frame_->stride(), output_rect.width(),
                         output_rect.height(), libyuv::kFilterBilinear);
+      output_data = output_frame_->data();
       output_frame_is_black_ = false;
     } else if (IsFrameUnpackedOrInverted(frame.get())) {
-      VLOG(2) << "  FrameUnpackedOrInverted";
       // If |frame| is not packed top-to-bottom then create a packed
       // top-to-bottom copy. This is required if the frame is inverted (see
       // crbug.com/306876), or if |frame| is cropped form a larger frame (see
@@ -548,44 +591,35 @@
       output_frame_->CopyPixelsFrom(
           *frame, webrtc::DesktopVector(),
           webrtc::DesktopRect::MakeSize(frame->size()));
+      output_data = output_frame_->data();
       output_frame_is_black_ = false;
     } else {
-      VLOG(2) << "  output_frame_ = std::move(frame)";
-      // If the captured frame matches the output size, we can use the incoming
-      // frame as is without any modifications.
-      output_frame_ = std::move(frame);
+      // If the captured frame matches the output size, we can return the pixel
+      // data directly.
+      output_data = frame->data();
       output_frame_is_black_ = false;
     }
   }
 
-  // Immediately send the new frame to the client and ask for a new frame.
-  SendLastReceivedFrameToClient(/*is_refresh_frame=*/false);
-  ScheduleNextCaptureFrame();
-}
+  gfx::ColorSpace frame_color_space;
+  if (!frame->icc_profile().empty()) {
+    gfx::ICCProfile icc_profile = gfx::ICCProfile::FromData(
+        frame->icc_profile().data(), frame->icc_profile().size());
+    frame_color_space = icc_profile.GetColorSpace();
+  }
 
-void DesktopCaptureDevice::Core::SendLastReceivedFrameToClient(
-    bool is_refresh_frame) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  TRACE_EVENT0("webrtc", __func__);
-  LogDesktopCaptureFrameIsRefresh(capturer_type_, is_refresh_frame);
-
-  size_t output_bytes = output_frame_->size().width() *
-                        output_frame_->size().height() *
-                        webrtc::DesktopFrame::kBytesPerPixel;
-
-  VLOG(2) << __func__ << " [output_size=(" << output_frame_->size().width()
-          << "x" << output_frame_->size().height() << ")]";
   base::TimeTicks now = NowTicks();
   if (first_ref_time_.is_null())
     first_ref_time_ = now;
   client_->OnIncomingCapturedData(
-      output_frame_->data(), output_bytes,
-      media::VideoCaptureFormat(gfx::Size(output_frame_->size().width(),
-                                          output_frame_->size().height()),
-                                requested_frame_rate_,
-                                media::PIXEL_FORMAT_ARGB),
-      output_frame_color_space_, 0 /* clockwise_rotation */, false /* flip_y */,
-      now, now - first_ref_time_);
+      output_data, output_bytes,
+      media::VideoCaptureFormat(
+          gfx::Size(output_size.width(), output_size.height()),
+          requested_frame_rate_, media::PIXEL_FORMAT_ARGB),
+      frame_color_space, 0 /* clockwise_rotation */, false /* flip_y */, now,
+      now - first_ref_time_);
+
+  ScheduleNextCaptureFrame();
 }
 
 void DesktopCaptureDevice::Core::OnCaptureTimer() {
@@ -594,17 +628,41 @@
   if (!client_)
     return;
 
-  CaptureFrame();
+  request_refresh_frame_timer_->Stop();
+  CaptureFrame(/*is_refresh_frame=*/false);
 }
 
-void DesktopCaptureDevice::Core::CaptureFrame() {
+void DesktopCaptureDevice::Core::OnRequestRefreshFrameTimer() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  DVLOG(2) << __func__;
+
+  if (!client_) {
+    return;
+  }
+
+  if (!capture_in_progress_) {
+    CaptureFrame(/*is_refresh_frame=*/true);
+  }
+}
+
+void DesktopCaptureDevice::Core::CaptureFrame(bool is_refresh_frame) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(!capture_in_progress_);
-  TRACE_EVENT0("webrtc", __func__);
-  VLOG(2) << __func__;
+  TRACE_EVENT1("webrtc", __func__, "is_refresh_frame", is_refresh_frame);
+  VLOG(2) << __func__ << "(is_refresh_frame=" << is_refresh_frame << ")";
+  LogDesktopCaptureFrameIsRefresh(capturer_type_, is_refresh_frame);
 
   capture_start_time_ = NowTicks();
-  capture_in_progress_ = true;
+
+  if (!is_refresh_frame) {
+    capture_in_progress_ = true;
+  } else {
+    refresh_in_progress_ = true;
+    // Cancel any outstanding refresh attempts when we are about to capture a
+    // frame the default way. The following is a no-op, if the timer was not
+    // running.
+    request_refresh_frame_timer_->Stop();
+  }
 
   if (last_capture_time_.is_null()) {
     last_capture_time_ = capture_start_time_;
@@ -643,10 +701,10 @@
   base::TimeDelta capture_period =
       std::max((last_capture_duration * 100) / max_cpu_consumption_percentage_,
                requested_frame_duration_);
-
   VLOG(2) << "  capture_period=" << capture_period.InMilliseconds();
   VLOG(2) << "  timer(dT="
           << (capture_period - last_capture_duration).InMilliseconds() << ")";
+
   // Schedule a task for the next frame.
   capture_timer_->Start(FROM_HERE, capture_period - last_capture_duration, this,
                         &Core::OnCaptureTimer);
diff --git a/content/browser/media/capture/desktop_capture_device.h b/content/browser/media/capture/desktop_capture_device.h
index dbca8bd..a401c7f 100644
--- a/content/browser/media/capture/desktop_capture_device.h
+++ b/content/browser/media/capture/desktop_capture_device.h
@@ -51,6 +51,11 @@
   void AllocateAndStart(const media::VideoCaptureParams& params,
                         std::unique_ptr<Client> client) override;
   void StopAndDeAllocate() override;
+  // If currently stopped, starts the refresh frame timer to guarantee a frame
+  // representing the most up-to-date content will be sent to the consumer in
+  // the near future. This refresh operation will be canceled if a default
+  // capture event triggers a frame capture in the meantime, and will result in
+  // a frame sent to the consumer with a delay of up to one second.
   void RequestRefreshFrame() override;
 
   // Set the platform-dependent window id for the notification window.
diff --git a/content/browser/media/capture/desktop_capture_device_unittest.cc b/content/browser/media/capture/desktop_capture_device_unittest.cc
index 6369fbc5..b2770776 100644
--- a/content/browser/media/capture/desktop_capture_device_unittest.cc
+++ b/content/browser/media/capture/desktop_capture_device_unittest.cc
@@ -44,6 +44,7 @@
 using ::testing::Expectation;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
+using ::testing::NiceMock;
 using ::testing::SaveArg;
 using ::testing::WithArg;
 
@@ -288,7 +289,8 @@
  protected:
   std::unique_ptr<media::MockVideoCaptureDeviceClient>
   CreateMockVideoCaptureDeviceClient() {
-    auto result = std::make_unique<media::MockVideoCaptureDeviceClient>();
+    auto result =
+        std::make_unique<NiceMock<media::MockVideoCaptureDeviceClient>>();
     ON_CALL(*result, ReserveOutputBuffer(_, _, _, _))
         .WillByDefault(Invoke([](const gfx::Size&,
                                  media::VideoPixelFormat format, int,
@@ -666,7 +668,12 @@
 
 // Verify that calling RequestRefreshFrame() results in a copy of the last
 // captured frame being sent to the client via OnIncomingCapturedData().
-TEST_F(DesktopCaptureDeviceTest, RequestRefreshFrameSendsLatestFrame) {
+//
+// TODO(crbug.com/1421656) The test is currently broken due to a change that
+// was required to fix a serious flickering issue. Attempts will be made to
+// enable this test again in a separate CL (and possibly in a new shape) once
+// the main fix has landed.
+TEST_F(DesktopCaptureDeviceTest, DISABLED_RequestRefreshFrameSendsLatestFrame) {
   FakeScreenCapturer* mock_capturer = new FakeScreenCapturer();
   CreateScreenCaptureDevice(
       std::unique_ptr<webrtc::DesktopCapturer>(mock_capturer));
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 216e29b7..0a82765 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -641,12 +641,7 @@
 
   if (!is_decoy) {
     prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchNotStarted);
-  }
-  prefetch_queue_.push_back(prefetch_container);
 
-  Prefetch();
-
-  if (!is_decoy) {
     // Registers a cookie listener for this prefetch if it is using an isolated
     // network context. If the cookies in the default partition associated with
     // this URL change after this point, then the prefetched resources should
@@ -658,6 +653,11 @@
                    ->GetCookieManagerForBrowserProcess());
     }
   }
+  prefetch_queue_.push_back(prefetch_container);
+
+  // Calling |Prefetch| could result in a prefetch being deleted, so
+  // |prefetch_cotnainer| should not be used after this call.
+  Prefetch();
 }
 
 void PrefetchService::OnGotEligibilityResultForRedirect(
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index 90db95d..149070a 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -2591,12 +2591,26 @@
                    /*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
+
+  VerifyCommonRequestState(GURL("https://example1.com"),
+                           /*use_prefetch_proxy=*/true);
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      /*use_prefetch_proxy=*/true,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+
   MakePrefetchOnMainFrame(
       GURL("https://example2.com"),
       PrefetchType(/*use_isolated_network_context=*/true,
                    /*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
+
+  VerifyCommonRequestState(GURL("https://example2.com"),
+                           /*use_prefetch_proxy=*/true);
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      /*use_prefetch_proxy=*/true,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+
   MakePrefetchOnMainFrame(
       GURL("https://example3.com"),
       PrefetchType(/*use_isolated_network_context=*/true,
@@ -2604,16 +2618,7 @@
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
-  VerifyCommonRequestState(GURL("https://example1.com"),
-                           /*use_prefetch_proxy=*/true);
-  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
-                      /*use_prefetch_proxy=*/true,
-                      {{"X-Testing", "Hello World"}}, kHTMLBody);
-  VerifyCommonRequestState(GURL("https://example2.com"),
-                           /*use_prefetch_proxy=*/true);
-  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
-                      /*use_prefetch_proxy=*/true,
-                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+  EXPECT_EQ(RequestCount(), 0);
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 3);
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
index 4eadcde..c78eca7 100644
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
@@ -4173,6 +4173,13 @@
       GetParam() == SSLPrerenderTestErrorBlockType::kClientCertRequested
           ? PrerenderFinalStatus::kClientCertRequested
           : PrerenderFinalStatus::kNavigationRequestNetworkError);
+  if (GetParam() == SSLPrerenderTestErrorBlockType::kClientCertRequested) {
+    return;
+  }
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.Experimental.PrerenderNavigationRequestNetworkErrorCode."
+      "SpeculationRule",
+      std::abs(net::Error::ERR_FAILED), 1);
 }
 
 // Tests that prerendering will be cancelled if the server asks for client
diff --git a/content/browser/preloading/prerender/prerender_host.cc b/content/browser/preloading/prerender/prerender_host.cc
index e09707e..7951d24 100644
--- a/content/browser/preloading/prerender/prerender_host.cc
+++ b/content/browser/preloading/prerender/prerender_host.cc
@@ -384,6 +384,11 @@
     status = PrerenderFinalStatus::kBlockedByClient;
   } else if (is_prerender_main_frame && net_error != net::Error::OK) {
     status = PrerenderFinalStatus::kNavigationRequestNetworkError;
+    if (!final_status_) {
+      // Only tracks the first error this instance encountered.
+      RecordPrerenderNavigationErrorCode(net_error, trigger_type(),
+                                         embedder_histogram_suffix());
+    }
   } else if (is_prerender_main_frame && !navigation_request->HasCommitted()) {
     status = PrerenderFinalStatus::kNavigationNotCommitted;
   }
diff --git a/content/browser/preloading/prerender/prerender_host_registry.cc b/content/browser/preloading/prerender/prerender_host_registry.cc
index 6398953a..b16b6420 100644
--- a/content/browser/preloading/prerender/prerender_host_registry.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry.cc
@@ -46,6 +46,16 @@
 
 namespace {
 
+// Kill-switch controlled by the field trial. When this feature is enabled,
+// PrerenderHostRegistry doesn't cancel prerendering even if query about the
+// current memory footprint fails. Now this is enabled by default as the query
+// frequently fails. Without the memory footprint check, the limit on the number
+// of ongoing prerendering requests and memory pressure events should prevent
+// excessive memory usage. See https://crbug.com/1444521 for details.
+BASE_FEATURE(kPrerender2IgnoreFailureOnMemoryFootprintQuery,
+             "Prerender2IgnoreFailureOnMemoryFootprintQuery",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 bool IsBackground(Visibility visibility) {
   // PrerenderHostRegistry treats HIDDEN and OCCLUDED as background.
   switch (visibility) {
@@ -1367,8 +1377,13 @@
     return;
   }
 
-  // Stop a prerendering when we can't get the current memory usage.
+  // Stop a prerendering or give up checking the memory consumption depending on
+  // the feature flag when we can't get the current memory usage.
   if (!success) {
+    if (base::FeatureList::IsEnabled(
+            kPrerender2IgnoreFailureOnMemoryFootprintQuery)) {
+      return;
+    }
     CancelHost(frame_tree_node_id, PrerenderFinalStatus::kFailToGetMemoryUsage);
     return;
   }
diff --git a/content/browser/preloading/prerender/prerender_metrics.cc b/content/browser/preloading/prerender/prerender_metrics.cc
index eda3940..d27fe05 100644
--- a/content/browser/preloading/prerender/prerender_metrics.cc
+++ b/content/browser/preloading/prerender/prerender_metrics.cc
@@ -398,6 +398,17 @@
       potential_activation_transition);
 }
 
+void RecordPrerenderNavigationErrorCode(
+    net::Error error_code,
+    PrerenderTriggerType trigger_type,
+    const std::string& embedder_histogram_suffix) {
+  base::UmaHistogramSparse(
+      GenerateHistogramName(
+          "Prerender.Experimental.PrerenderNavigationRequestNetworkErrorCode",
+          trigger_type, embedder_histogram_suffix),
+      std::abs(error_code));
+}
+
 static_assert(
     static_cast<int>(PrerenderBackNavigationEligibility::kMaxValue) +
         static_cast<int>(
diff --git a/content/browser/preloading/prerender/prerender_metrics.h b/content/browser/preloading/prerender/prerender_metrics.h
index d7aad085..20e69a0 100644
--- a/content/browser/preloading/prerender/prerender_metrics.h
+++ b/content/browser/preloading/prerender/prerender_metrics.h
@@ -158,6 +158,15 @@
     PrerenderTriggerType trigger_type,
     const std::string& embedder_histogram_suffix);
 
+// Records the net error code when a prerendering page fails the navigation upon
+// PrerenderHost::DidFinishNavigation. Note that this is different from the one
+// that checked at `PrerenderNavigationThrottle::WillProcessResponse` which
+// checks the http_response_code of the `commit_params`.
+void RecordPrerenderNavigationErrorCode(
+    net::Error error_code,
+    PrerenderTriggerType trigger_type,
+    const std::string& embedder_histogram_suffix);
+
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 // These are also mapped onto the second content internal range of
diff --git a/content/browser/renderer_host/browsing_context_state.cc b/content/browser/renderer_host/browsing_context_state.cc
index 5cf5ab2..33704e35 100644
--- a/content/browser/renderer_host/browsing_context_state.cc
+++ b/content/browser/renderer_host/browsing_context_state.cc
@@ -38,11 +38,11 @@
     blink::mojom::FrameReplicationStatePtr replication_state,
     RenderFrameHostImpl* parent,
     absl::optional<BrowsingInstanceId> browsing_instance_id,
-    absl::optional<CoopRelatedGroupId> coop_related_group_id)
+    absl::optional<base::UnguessableToken> coop_related_group_token)
     : replication_state_(std::move(replication_state)),
       parent_(parent),
       browsing_instance_id_(browsing_instance_id),
-      coop_related_group_id_(coop_related_group_id) {
+      coop_related_group_token_(coop_related_group_token) {
   TRACE_EVENT_BEGIN("navigation", "BrowsingContextState",
                     perfetto::Track::FromPointer(this),
                     "browsing_context_state_when_created", this);
@@ -87,8 +87,8 @@
     // Note: Outer delegates are an exception, and when we're expecting to
     // interact with one, we should pass in the proper `proxy_access_mode` to
     // not end up in this condition.
-    CHECK_EQ(coop_related_group_id_.value(),
-             site_instance_group->GetCoopRelatedGroupId());
+    CHECK_EQ(coop_related_group_token_.value(),
+             site_instance_group->coop_related_group_token());
   }
   auto it = proxy_hosts_.find(site_instance_group->GetId());
   if (it != proxy_hosts_.end()) {
@@ -105,8 +105,8 @@
               kSwapForCrossBrowsingInstanceNavigations &&
       proxy_access_mode == ProxyAccessMode::kRegular) {
     // See comments in GetRenderFrameProxyHost for why this check is needed.
-    CHECK_EQ(coop_related_group_id_.value(),
-             site_instance_group->GetCoopRelatedGroupId());
+    CHECK_EQ(coop_related_group_token_.value(),
+             site_instance_group->coop_related_group_token());
   }
   TRACE_EVENT("navigation", "BrowsingContextState::DeleteRenderFrameProxyHost",
               ChromeTrackEvent::kBrowsingContextState, this,
@@ -140,8 +140,8 @@
               kSwapForCrossBrowsingInstanceNavigations &&
       proxy_access_mode == ProxyAccessMode::kRegular) {
     // See comments in GetRenderFrameProxyHost for why this check is needed.
-    CHECK_EQ(coop_related_group_id_.value(),
-             site_instance->GetCoopRelatedGroupId());
+    CHECK_EQ(coop_related_group_token_.value(),
+             site_instance->coop_related_group_token());
   }
 
   auto site_instance_group_id = site_instance->group()->GetId();
@@ -484,8 +484,9 @@
     proto->set_browsing_instance_id(browsing_instance_id_.value().value());
   }
 
-  if (coop_related_group_id_.has_value()) {
-    proto->set_coop_related_group_id(coop_related_group_id_.value().value());
+  if (coop_related_group_token_.has_value()) {
+    proto->set_coop_related_group_token(
+        coop_related_group_token_.value().ToString());
   }
 
   perfetto::TracedDictionary dict = std::move(proto).AddDebugAnnotations();
diff --git a/content/browser/renderer_host/browsing_context_state.h b/content/browser/renderer_host/browsing_context_state.h
index 2f7098bd..0057a9b 100644
--- a/content/browser/renderer_host/browsing_context_state.h
+++ b/content/browser/renderer_host/browsing_context_state.h
@@ -8,6 +8,7 @@
 #include "base/feature_list.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/safe_ref.h"
+#include "base/unguessable_token.h"
 #include "content/browser/coop_related_group.h"
 #include "content/browser/renderer_host/render_frame_proxy_host.h"
 #include "content/browser/site_instance_group.h"
@@ -88,7 +89,7 @@
       blink::mojom::FrameReplicationStatePtr replication_state,
       RenderFrameHostImpl* parent,
       absl::optional<BrowsingInstanceId> browsing_instance_id,
-      absl::optional<CoopRelatedGroupId> coop_related_group_id);
+      absl::optional<base::UnguessableToken> coop_related_group_token);
 
   // Returns a const reference to the map of proxy hosts. The keys are
   // SiteInstanceGroup IDs, the values are RenderFrameProxyHosts.
@@ -299,16 +300,15 @@
   // main frame BrowsingContextState.
   const raw_ptr<RenderFrameHostImpl> parent_;
 
-  // ID of the BrowsingInstance and CoopRelatedGroup to which this
+  // ID of the BrowsingInstance and token of the CoopRelatedGroup to which this
   // BrowsingContextState belongs. Currently `browsing_instance_id` and
-  // `coop_related_group_id` will be null iff the legacy mode is enabled, as the
-  // legacy mode BrowsingContextState is 1:1 with FrameTreeNode and therefore
-  // doesn't have a dedicated associated BrowsingInstance or CoopRelatedGroup.
-  // TODO(crbug.com/1270671): Make `browsing_instance_id` and
-  // `coop_related_group_id` non-optional when the legacy path
-  // is removed.
+  // `coop_related_group_token` will be null iff the legacy mode is enabled, as
+  // the legacy mode BrowsingContextState is 1:1 with FrameTreeNode and
+  // therefore doesn't have a dedicated associated BrowsingInstance or
+  // CoopRelatedGroup. TODO(crbug.com/1270671): Make `browsing_instance_id` and
+  // `coop_related_group_token` non-optional when the legacy path is removed.
   const absl::optional<BrowsingInstanceId> browsing_instance_id_;
-  const absl::optional<CoopRelatedGroupId> coop_related_group_id_;
+  const absl::optional<base::UnguessableToken> coop_related_group_token_;
 
   base::WeakPtrFactory<BrowsingContextState> weak_factory_{this};
 };
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index ffb84b6..882184e 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -48,6 +48,7 @@
 #include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/common/surfaces/surface_range.h"
 #include "components/viz/common/viz_utils.h"
 #include "components/viz/host/host_display_client.h"
 #include "content/browser/browser_main_loop.h"
@@ -314,16 +315,24 @@
 class CompositorImpl::ReadbackRefImpl
     : public ui::WindowAndroidCompositor::ReadbackRef {
  public:
-  explicit ReadbackRefImpl(base::WeakPtr<CompositorImpl> weakptr);
+  ReadbackRefImpl(base::WeakPtr<CompositorImpl> weakptr,
+                  const viz::SurfaceId& surface_id_to_copy);
   ~ReadbackRefImpl() override;
 
  private:
   base::WeakPtr<CompositorImpl> compositor_weakptr_;
+  std::unique_ptr<cc::slim::LayerTree::ScopedKeepSurfaceAlive> keep_alive_;
 };
 
 CompositorImpl::ReadbackRefImpl::ReadbackRefImpl(
-    base::WeakPtr<CompositorImpl> weakptr)
-    : compositor_weakptr_(weakptr) {}
+    base::WeakPtr<CompositorImpl> weakptr,
+    const viz::SurfaceId& surface_id_to_copy)
+    : compositor_weakptr_(std::move(weakptr)) {
+  DCHECK(compositor_weakptr_);
+  DCHECK(compositor_weakptr_->host_);
+  keep_alive_ = compositor_weakptr_->host_->CreateScopedKeepSurfaceAlive(
+      surface_id_to_copy);
+}
 
 CompositorImpl::ReadbackRefImpl::~ReadbackRefImpl() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -759,9 +768,11 @@
 }
 
 std::unique_ptr<ui::WindowAndroidCompositor::ReadbackRef>
-CompositorImpl::TakeReadbackRef() {
+CompositorImpl::TakeReadbackRef(const viz::SurfaceId& surface_id) {
+  DCHECK(surface_id.is_valid());
   ++pending_readbacks_;
-  return std::make_unique<ReadbackRefImpl>(weak_factory_.GetWeakPtr());
+  return std::make_unique<ReadbackRefImpl>(weak_factory_.GetWeakPtr(),
+                                           surface_id);
 }
 
 void CompositorImpl::RequestCopyOfOutputOnRootLayer(
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index c9685d3..645cf06 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -89,6 +89,7 @@
       base::RepeatingCallback<void(const gfx::Size&)> cb) {
     swap_completed_with_size_for_testing_ = std::move(cb);
   }
+  cc::slim::LayerTree* GetLayerTreeForTesting() const { return host_.get(); }
 
   class SimpleBeginFrameObserver {
    public:
@@ -136,7 +137,8 @@
   void DidLoseLayerTreeFrameSink() override;
 
   // WindowAndroidCompositor implementation.
-  std::unique_ptr<ReadbackRef> TakeReadbackRef() override;
+  std::unique_ptr<ReadbackRef> TakeReadbackRef(
+      const viz::SurfaceId& surface_id) override;
   void RequestCopyOfOutputOnRootLayer(
       std::unique_ptr<viz::CopyOutputRequest> request) override;
   void SetNeedsAnimate() override;
diff --git a/content/browser/renderer_host/input/synthetic_pointer_action.h b/content/browser/renderer_host/input/synthetic_pointer_action.h
index 0675d22..3e67d69 100644
--- a/content/browser/renderer_host/input/synthetic_pointer_action.h
+++ b/content/browser/renderer_host/input/synthetic_pointer_action.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "content/browser/renderer_host/input/synthetic_gesture.h"
 #include "content/browser/renderer_host/input/synthetic_gesture_target.h"
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 7baf55c..6a4288d6 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -303,18 +303,12 @@
              "DisableFrameNameUpdateOnNonCurrentRenderFrameHost",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Evict when accessibility events occur while in back/forward cache. Remove
-// once the https://crbug.com/1341507 is resolved. This crash started to happen
-// on Android with bfcache experiments, so we're enabling this flag only on
-// Android.
+// Evict when accessibility events occur while in back/forward cache.
+// Disabling on all platforms since https://crbug.com/1341507 has been addressed
+// and no significant crashes are happening with experiments.
 BASE_FEATURE(kEvictOnAXEvents,
              "EvictOnAXEvents",
-#if BUILDFLAG(IS_ANDROID)
-             base::FEATURE_ENABLED_BY_DEFAULT
-#else
-             base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kUnblockSpeechSynthesisForBFCache,
              "UnblockSpeechSynthesisForBFCache",
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 6b930ba..0f9f741 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -448,8 +448,9 @@
               ? static_cast<absl::optional<BrowsingInstanceId>>(absl::nullopt)
               : site_instance->GetBrowsingInstanceId(),
           is_legacy_browsing_context_state_mode
-              ? static_cast<absl::optional<CoopRelatedGroupId>>(absl::nullopt)
-              : site_instance->GetCoopRelatedGroupId());
+              ? static_cast<absl::optional<base::UnguessableToken>>(
+                    absl::nullopt)
+              : site_instance->coop_related_group_token());
   browsing_context_state->CommitFramePolicy(initial_main_frame_policy);
   browsing_context_state->SetFrameName(name, "");
   UpdateProcessReusePolicyForProcessPerSiteWithMainFrameThreshold(
@@ -499,8 +500,9 @@
               ? static_cast<absl::optional<BrowsingInstanceId>>(absl::nullopt)
               : site_instance->GetBrowsingInstanceId(),
           is_legacy_browsing_context_state_mode
-              ? static_cast<absl::optional<CoopRelatedGroupId>>(absl::nullopt)
-              : site_instance->GetCoopRelatedGroupId());
+              ? static_cast<absl::optional<base::UnguessableToken>>(
+                    absl::nullopt)
+              : site_instance->coop_related_group_token());
   browsing_context_state->CommitFramePolicy(frame_policy);
   SetRenderFrameHost(CreateRenderFrameHost(
       CreateFrameCase::kInitChild, site_instance, frame_routing_id,
@@ -3458,7 +3460,7 @@
                 ->current_replication_state()
                 .Clone(),
             frame_tree_node_->parent(), new_instance->GetBrowsingInstanceId(),
-            new_instance->GetCoopRelatedGroupId());
+            new_instance->coop_related_group_token());
 
         // Add a proxy to the outer delegate if one exists, as this is not
         // copied over to the new BrowsingContextState otherwise.
diff --git a/content/browser/renderer_host/render_widget_host_view_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_browsertest.cc
index 7acc2061..e0e5097e 100644
--- a/content/browser/renderer_host/render_widget_host_view_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_browsertest.cc
@@ -23,6 +23,8 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "cc/slim/features.h"
+#include "cc/slim/layer_tree.h"
 #include "cc/slim/surface_layer.h"
 #include "content/browser/gpu/compositor_util.h"
 #include "content/browser/renderer_host/dip_util.h"
@@ -64,6 +66,7 @@
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
+#include "content/browser/renderer_host/compositor_impl_android.h"
 #include "content/browser/renderer_host/render_widget_host_view_android.h"
 #include "ui/android/delegated_frame_host_android.h"
 #endif
@@ -1364,4 +1367,66 @@
 
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_ANDROID)
+void CheckSurfaceRangeRemovedAfterCopy(viz::SurfaceRange range,
+                                       CompositorImpl* compositor,
+                                       base::RepeatingClosure resume_test,
+                                       const SkBitmap& btimap) {
+  ASSERT_FALSE(!compositor->GetLayerTreeForTesting()
+                    ->GetSurfaceRangesForTesting()
+                    .contains(range));
+  std::move(resume_test).Run();
+}
+
+class RenderWidgetHostViewCopyFromSurfaceBrowserTest
+    : public RenderWidgetHostViewBrowserTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  RenderWidgetHostViewCopyFromSurfaceBrowserTest() {
+    if (GetParam()) {
+      scoped_feature_list_.InitAndEnableFeature(features::kSlimCompositor);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(features::kSlimCompositor);
+    }
+  }
+  ~RenderWidgetHostViewCopyFromSurfaceBrowserTest() override = default;
+
+  bool SetUpSourceSurface(const char* wait_message) override { return false; }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(RenderWidgetHostViewCopyFromSurfaceBrowserTest,
+                       AsyncCopyFromSurface) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  EXPECT_TRUE(
+      NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
+
+  auto* rwhv_android = static_cast<RenderWidgetHostViewAndroid*>(
+      GetRenderViewHost()->GetWidget()->GetView());
+  auto* compositor = static_cast<CompositorImpl*>(
+      rwhv_android->GetNativeView()->GetWindowAndroid()->GetCompositor());
+
+  const viz::SurfaceRange range_for_copy(rwhv_android->GetCurrentSurfaceId(),
+                                         rwhv_android->GetCurrentSurfaceId());
+  const viz::SurfaceRange range_for_mainframe(
+      absl::nullopt, rwhv_android->GetCurrentSurfaceId());
+  base::RunLoop run_loop;
+  GetRenderViewHost()->GetWidget()->GetView()->CopyFromSurface(
+      gfx::Rect(), gfx::Size(),
+      base::BindOnce(&CheckSurfaceRangeRemovedAfterCopy, range_for_copy,
+                     compositor, run_loop.QuitClosure()));
+  EXPECT_THAT(
+      compositor->GetLayerTreeForTesting()->GetSurfaceRangesForTesting(),
+      testing::UnorderedElementsAre(std::make_pair(range_for_copy, 1),
+                                    std::make_pair(range_for_mainframe, 1)));
+  run_loop.Run(FROM_HERE);
+}
+
+INSTANTIATE_TEST_SUITE_P(EnableDisableSlim,
+                         RenderWidgetHostViewCopyFromSurfaceBrowserTest,
+                         ::testing::Bool());
+#endif
+
 }  // namespace content
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts
index 0e03c8e..657cb88 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.ts
+++ b/content/browser/resources/attribution_reporting/attribution_internals.ts
@@ -535,9 +535,8 @@
   }
 }
 
-function commonReportTableColumns<T extends Report>(): Array<Column<T>> {
+function commonPreReportTableColumns<T extends Report>(): Array<Column<T>> {
   return [
-    new CodeColumn<T>('Report Body', (e) => e.reportBody),
     new ValueColumn<T, string>('Status', (e) => e.status),
     new ReportUrlColumn<T>(),
     new DateColumn<T>('Trigger Time', (e) => e.triggerTime),
@@ -545,6 +544,12 @@
   ];
 }
 
+function commonPostReportTableColumns<T extends Report>(): Array<Column<T>> {
+  return [
+    new CodeColumn<T>('Report Body', (e) => e.reportBody),
+  ];
+}
+
 class ReportTableModel<T extends Report> extends TableModel<T> {
   private readonly showDebugReportsCheckbox: HTMLInputElement;
   private readonly hiddenDebugReportsSpan: HTMLSpanElement;
@@ -557,8 +562,9 @@
       private readonly sendReportsButton: HTMLButtonElement,
       private readonly handler: HandlerInterface) {
     super(
-        commonReportTableColumns<T>().concat(cols),
-        5,  // Sort by report time by default; the extra column is added below
+        commonPreReportTableColumns<T>().concat(cols)
+            .concat(commonPostReportTableColumns<T>()),
+        4,  // Sort by report time by default; the extra column is added below
         'No sent or pending reports.',
     );
 
diff --git a/content/browser/scheduler/browser_task_executor.cc b/content/browser/scheduler/browser_task_executor.cc
index 818e5f1..69f9daf 100644
--- a/content/browser/scheduler/browser_task_executor.cc
+++ b/content/browser/scheduler/browser_task_executor.cc
@@ -30,21 +30,6 @@
 using QueueType = content::BrowserTaskQueues::QueueType;
 
 namespace content {
-namespace features {
-// When the "BrowserPrioritizeInputQueue" feature is enabled, the browser will
-// schedule tasks related to input in kHigh priority queue. This puts it under
-// bootstrap, but above regular tasks.
-//
-// The goal is to reduce jank by ensuring chromium is handling input events as
-// soon as possible.
-//
-// TODO(nuskos): Remove this feature flag after we've done our retroactive study
-// of all chrometto performance improvements.
-BASE_FEATURE(kBrowserPrioritizeInputQueue,
-             "BrowserPrioritizeInputQueue",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-}  // namespace features
 
 namespace {
 
@@ -100,12 +85,7 @@
     const BrowserTaskTraits& traits) {
   switch (traits.task_type()) {
     case BrowserTaskType::kUserInput:
-      if (base::FeatureList::IsEnabled(
-              features::kBrowserPrioritizeInputQueue)) {
-        return QueueType::kUserInput;
-      }
-      // Defer to traits.priority() below.
-      break;
+      return QueueType::kUserInput;
 
     case BrowserTaskType::kNavigationNetworkResponse:
       if (base::FeatureList::IsEnabled(
diff --git a/content/browser/site_instance_group.cc b/content/browser/site_instance_group.cc
index 087c33e..dc220ea 100644
--- a/content/browser/site_instance_group.cc
+++ b/content/browser/site_instance_group.cc
@@ -79,7 +79,7 @@
 
 bool SiteInstanceGroup::IsCoopRelatedSiteInstanceGroup(
     SiteInstanceGroup* group) {
-  return GetCoopRelatedGroupId() == group->GetCoopRelatedGroupId();
+  return coop_related_group_token() == group->coop_related_group_token();
 }
 
 void SiteInstanceGroup::RenderProcessHostDestroyed(RenderProcessHost* host) {
@@ -129,8 +129,4 @@
   proto.Set(TraceProto::kProcess, process());
 }
 
-CoopRelatedGroupId SiteInstanceGroup::GetCoopRelatedGroupId() const {
-  return browsing_instance_->GetCoopRelatedGroupId();
-}
-
 }  // namespace content
diff --git a/content/browser/site_instance_group.h b/content/browser/site_instance_group.h
index d94f14e..17a7a73 100644
--- a/content/browser/site_instance_group.h
+++ b/content/browser/site_instance_group.h
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/observer_list.h"
 #include "base/types/id_type.h"
+#include "base/unguessable_token.h"
 #include "content/browser/browsing_instance.h"
 #include "content/browser/coop_related_group.h"
 #include "content/browser/renderer_host/agent_scheduling_group_host.h"
@@ -136,8 +137,11 @@
     return browsing_instance_->isolation_context().browsing_instance_id();
   }
 
-  // Returns the ID of the CoopRelatedGroup this SiteInstanceGroup belongs to.
-  CoopRelatedGroupId GetCoopRelatedGroupId() const;
+  // Returns the token uniquely identifying the CoopRelatedGroup this
+  // SiteInstanceGroup belongs to.
+  base::UnguessableToken coop_related_group_token() const {
+    return browsing_instance_->coop_related_group_token();
+  }
 
   AgentSchedulingGroupHost& agent_scheduling_group() {
     DCHECK_EQ(agent_scheduling_group_->GetProcess(), &*process_);
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index 845ae3b..84e6de8 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -13,7 +13,6 @@
 #include "base/lazy_instance.h"
 #include "base/trace_event/typed_macros.h"
 #include "content/browser/bad_message.h"
-#include "content/browser/browsing_instance.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/isolated_origin_util.h"
 #include "content/browser/isolation_context.h"
@@ -1497,12 +1496,8 @@
 
 bool SiteInstanceImpl::IsCoopRelatedSiteInstance(
     const SiteInstanceImpl* instance) const {
-  return instance->browsing_instance_->GetCoopRelatedGroupId() ==
-         browsing_instance_->GetCoopRelatedGroupId();
-}
-
-CoopRelatedGroupId SiteInstanceImpl::GetCoopRelatedGroupId() const {
-  return browsing_instance_->GetCoopRelatedGroupId();
+  return instance->browsing_instance_->coop_related_group_token() ==
+         browsing_instance_->coop_related_group_token();
 }
 
 void SiteInstanceImpl::SetProcessForTesting(RenderProcessHost* process) {
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index 60bdb678..a1d224e 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -10,6 +10,7 @@
 
 #include "base/check.h"
 #include "base/memory/scoped_refptr.h"
+#include "content/browser/browsing_instance.h"
 #include "content/browser/coop_related_group.h"
 #include "content/browser/isolation_context.h"
 #include "content/browser/site_info.h"
@@ -30,7 +31,6 @@
 
 class AgentSchedulingGroupHost;
 class BrowserContext;
-class BrowsingInstance;
 class SiteInstanceGroup;
 class StoragePartitionConfig;
 class StoragePartitionImpl;
@@ -459,10 +459,18 @@
   // same BrowsingInstance, they are related and COOP related.
   bool IsCoopRelatedSiteInstance(const SiteInstanceImpl* instance) const;
 
-  // Returns the ID of the CoopRelatedGroup this SiteInstance belongs to. Prefer
-  // using IsCoopRelatedSiteInstance() instead of directly comparing IDs to know
-  // if two SiteInstances belong to the same CoopRelatedGroup.
-  CoopRelatedGroupId GetCoopRelatedGroupId() const;
+  // Returns the token uniquely identifying the BrowsingInstance this
+  // SiteInstance belongs to. Can safely be sent to the renderer unlike the
+  // BrowsingInstanceID.
+  base::UnguessableToken browsing_instance_token() const {
+    return browsing_instance_->token();
+  }
+
+  // Returns the token uniquely identifying the CoopRelatedGroup this
+  // SiteInstance belongs to. Can safely be sent to the renderer.
+  base::UnguessableToken coop_related_group_token() const {
+    return browsing_instance_->coop_related_group_token();
+  }
 
   // Returns the unique origin of all top-level documents in this
   // BrowsingInstance. This is only guaranteed by the use of a unique COOP value
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index 00eeee6..bf50e539 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -2196,4 +2196,82 @@
   EXPECT_TRUE(derived_instance->IsCoopRelatedSiteInstance(base_instance.get()));
 }
 
+TEST_F(SiteInstanceTest, GroupTokensBuilding) {
+  const GURL test_url("https://example.com");
+  const auto base_instance = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo(UrlInfoInit(test_url)), /*is_guest=*/false,
+      /*is_fenced=*/false);
+
+  base::UnguessableToken browsing_instance_token =
+      base_instance->browsing_instance_token();
+  base::UnguessableToken coop_related_group_token =
+      base_instance->coop_related_group_token();
+  EXPECT_NE(browsing_instance_token, coop_related_group_token);
+}
+
+TEST_F(SiteInstanceTest, GroupTokensRelatedSiteInstances) {
+  const GURL test_url("https://example.com");
+  const auto base_instance = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo(UrlInfoInit(test_url)), /*is_guest=*/false,
+      /*is_fenced=*/false);
+
+  const auto derived_instance = base_instance->GetRelatedSiteInstanceImpl(
+      UrlInfo(UrlInfoInit(GURL("https://other-example.com"))));
+
+  // Without full Site Isolation, we'll group different sites in the default
+  // SiteInstance.
+  if (AreDefaultSiteInstancesEnabled()) {
+    EXPECT_EQ(derived_instance.get(), base_instance.get());
+    return;
+  }
+
+  EXPECT_NE(derived_instance.get(), base_instance.get());
+  EXPECT_TRUE(derived_instance->IsRelatedSiteInstance(base_instance.get()));
+  EXPECT_EQ(derived_instance->browsing_instance_token(),
+            base_instance->browsing_instance_token());
+  EXPECT_EQ(derived_instance->coop_related_group_token(),
+            base_instance->coop_related_group_token());
+}
+
+TEST_F(SiteInstanceTest, GroupTokensCoopRelatedSiteInstances) {
+  const GURL test_url("https://example.com");
+  const auto base_instance = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo(UrlInfoInit(test_url)), /*is_guest=*/false,
+      /*is_fenced=*/false);
+
+  // Derive a SiteInstance that lives in the same CoopRelatedGroup but a
+  // different BrowsingInstance. Provide a different WebExposedIsolationInfo to
+  // make sure we do not reuse the BrowsingInstance.
+  const auto derived_instance = base_instance->GetCoopRelatedSiteInstanceImpl(
+      UrlInfo(UrlInfoInit(test_url).WithWebExposedIsolationInfo(
+          WebExposedIsolationInfo::CreateIsolated(
+              url::Origin::Create(test_url)))));
+  EXPECT_NE(derived_instance.get(), base_instance.get());
+  EXPECT_FALSE(derived_instance->IsRelatedSiteInstance(base_instance.get()));
+  EXPECT_TRUE(derived_instance->IsCoopRelatedSiteInstance(base_instance.get()));
+  EXPECT_NE(derived_instance->browsing_instance_token(),
+            base_instance->browsing_instance_token());
+  EXPECT_EQ(derived_instance->coop_related_group_token(),
+            base_instance->coop_related_group_token());
+}
+
+TEST_F(SiteInstanceTest, GroupTokensUnrelatedSiteInstances) {
+  const GURL test_url("https://example.com");
+  const auto base_instance = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo(UrlInfoInit(test_url)), /*is_guest=*/false,
+      /*is_fenced=*/false);
+
+  const auto other_instance = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo(UrlInfoInit(test_url)), /*is_guest=*/false,
+      /*is_fenced=*/false);
+
+  EXPECT_NE(other_instance.get(), base_instance.get());
+  EXPECT_FALSE(other_instance->IsRelatedSiteInstance(base_instance.get()));
+  EXPECT_FALSE(other_instance->IsCoopRelatedSiteInstance(base_instance.get()));
+  EXPECT_NE(other_instance->browsing_instance_token(),
+            base_instance->browsing_instance_token());
+  EXPECT_NE(other_instance->coop_related_group_token(),
+            base_instance->coop_related_group_token());
+}
+
 }  // namespace content
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 9d4e600..456054b 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -824,24 +824,6 @@
 #endif
 };
 
-// This tests the kInputTargetClientHighPriority finch experiment where we
-// upgrade the TaskQueue priority for InputTargetClient methods.
-class SitePerProcessHitTestTaskPriorityBrowserTest
-    : public SitePerProcessHitTestBrowserTest {
- public:
-  SitePerProcessHitTestTaskPriorityBrowserTest() = default;
-
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    SitePerProcessBrowserTestBase::SetUpCommandLine(command_line);
-    feature_list_.InitAndEnableFeature(
-        blink::features::kInputTargetClientHighPriority);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
 //
 // SitePerProcessHighDPIHitTestBrowserTest
 //
@@ -6746,13 +6728,6 @@
   HitTestNestedFramesHelper(shell(), embedded_test_server());
 }
 
-// Test that the InputTargetClient interface works as expected even when Running
-// a TaskPriority finch experiment.
-IN_PROC_BROWSER_TEST_F(SitePerProcessHitTestTaskPriorityBrowserTest,
-                       SmokeTestInputTargetClientTaskPriority) {
-  HitTestNestedFramesHelper(shell(), embedded_test_server());
-}
-
 IN_PROC_BROWSER_TEST_F(SitePerProcessHitTestBrowserTest,
                        HitTestOOPIFWithPaddingAndBorder) {
   GURL main_url(embedded_test_server()->GetURL(
diff --git a/content/browser/smart_card/smart_card_browsertest.cc b/content/browser/smart_card/smart_card_browsertest.cc
index 1fa5b26..8fd083a 100644
--- a/content/browser/smart_card/smart_card_browsertest.cc
+++ b/content/browser/smart_card/smart_card_browsertest.cc
@@ -1035,4 +1035,84 @@
       SmartCardResult::NewSuccess(SmartCardSuccess::kOk));
 }
 
+IN_PROC_BROWSER_TEST_F(SmartCardTest, Transmit) {
+  ASSERT_TRUE(NavigateToURL(shell(), GetIsolatedContextUrl()));
+
+  MockSmartCardReaderTracker& mock_tracker = CreateMockSmartCardReaderTracker();
+
+  MockSmartCardContextFactory& mock_context_factory =
+      GetFakeSmartCardDelegate().mock_context_factory;
+  MockSmartCardConnection mock_connection;
+  mojo::Receiver<SmartCardConnection> connection_receiver(&mock_connection);
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(mock_tracker, Start(_, _))
+        .WillOnce(
+            [&mock_tracker](SmartCardReaderTracker::Observer* observer,
+                            SmartCardReaderTracker::StartCallback callback) {
+              mock_tracker.observer_list.AddObserverIfMissing(observer);
+
+              std::vector<blink::mojom::SmartCardReaderInfoPtr> readers;
+              readers.push_back(blink::mojom::SmartCardReaderInfo::New(
+                  "Fake reader", blink::mojom::SmartCardReaderState::kEmpty,
+                  std::vector<uint8_t>()));
+              std::move(callback).Run(
+                  blink::mojom::SmartCardGetReadersResult::NewReaders(
+                      std::move(readers)));
+            });
+
+    EXPECT_CALL(mock_context_factory,
+                Connect("Fake reader", SmartCardShareMode::kShared, _, _))
+        .WillOnce([&connection_receiver](
+                      const std::string& reader,
+                      device::mojom::SmartCardShareMode share_mode,
+                      device::mojom::SmartCardProtocolsPtr preferred_protocols,
+                      SmartCardContext::ConnectCallback callback) {
+          EXPECT_FALSE(preferred_protocols->t0);
+          EXPECT_TRUE(preferred_protocols->t1);
+          EXPECT_FALSE(preferred_protocols->raw);
+
+          auto success = device::mojom::SmartCardConnectSuccess::New(
+              connection_receiver.BindNewPipeAndPassRemote(),
+              SmartCardProtocol::kT1);
+
+          std::move(callback).Run(
+              device::mojom::SmartCardConnectResult::NewSuccess(
+                  std::move(success)));
+        });
+
+    EXPECT_CALL(mock_connection, Transmit(SmartCardProtocol::kT1, _, _))
+        .WillOnce([](SmartCardProtocol protocol,
+                     const std::vector<uint8_t>& data,
+                     SmartCardConnection::TransmitCallback callback) {
+          EXPECT_EQ(data, std::vector<uint8_t>({3u, 2u, 1u}));
+          std::move(callback).Run(
+              device::mojom::SmartCardDataResult::NewData({12u, 34u}));
+        });
+
+    // When the document is destroyed
+    EXPECT_CALL(mock_tracker, Stop(_));
+  }
+
+  EXPECT_EQ("response: 12,34", EvalJs(shell(), R"(
+    (async () => {
+      let readers = await navigator.smartCard.getReaders();
+
+      if (readers.length !== 1) {
+        return "reader not found";
+      }
+
+      let reader = readers[0];
+      let connection = await reader.connect("shared", ["t1"]);
+
+      let apdu = new Uint8Array([0x03, 0x02, 0x01]);
+      let response = await connection.transmit(apdu);
+
+      let responseString = new Uint8Array(response).toString();
+      return `response: ${responseString}`;
+    })())"));
+}
+
 }  // namespace content
diff --git a/content/browser/webid/fake_identity_request_dialog_controller.cc b/content/browser/webid/fake_identity_request_dialog_controller.cc
index 0d0305f..1712d509 100644
--- a/content/browser/webid/fake_identity_request_dialog_controller.cc
+++ b/content/browser/webid/fake_identity_request_dialog_controller.cc
@@ -63,13 +63,10 @@
   return title_;
 }
 
-void FakeIdentityRequestDialogController::ShowModalDialog(
+content::WebContents* FakeIdentityRequestDialogController::ShowModalDialog(
     const GURL& url,
-    TokenCallback on_resolve,
     DismissCallback dismiss_callback) {
-  // Pretends that the url is loaded and calls the
-  // IdentityProvider.resolve() method with the fake token below.
-  std::move(on_resolve).Run("--fake-token-from-pop-up-window--");
+  return nullptr;
 }
 
 }  // namespace content
diff --git a/content/browser/webid/fake_identity_request_dialog_controller.h b/content/browser/webid/fake_identity_request_dialog_controller.h
index a041a06..ef91c62d 100644
--- a/content/browser/webid/fake_identity_request_dialog_controller.h
+++ b/content/browser/webid/fake_identity_request_dialog_controller.h
@@ -34,9 +34,9 @@
 
   std::string GetTitle() const override;
 
-  void ShowModalDialog(const GURL& url,
-                       TokenCallback on_resolve,
-                       DismissCallback dismiss_callback) override;
+  content::WebContents* ShowModalDialog(
+      const GURL& url,
+      DismissCallback dismiss_callback) override;
 
  private:
   absl::optional<std::string> selected_account_;
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 8b0b8d0..82af944 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -807,9 +807,14 @@
     std::move(callback).Run(false);
     return;
   }
-  // TODO(crbug.com/1429083): Implement the resolution of the RequestToken
-  // call with the token that is passed here.
-  std::move(callback).Run(true);
+
+  if (!identity_registry_) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  bool accepted = identity_registry_->NotifyResolve(origin(), token);
+  std::move(callback).Run(accepted);
 }
 
 void FederatedAuthRequestImpl::SetIdpSigninStatus(
@@ -1290,19 +1295,14 @@
                      FederatedAuthRequestResult::kError,
                      TokenStatus::kNotSignedInWithIdp,
                      /*should_delay_callback=*/true),
-      base::BindOnce(&FederatedAuthRequestImpl::CreateIdentityRegistry,
-                     weak_ptr_factory_.GetWeakPtr(), idp_origin));
-}
-
-void FederatedAuthRequestImpl::CreateIdentityRegistry(
-    const url::Origin& idp_origin,
-    content::WebContents* web_contents) {
-  IdentityRegistry::CreateForWebContents(web_contents, this, idp_origin);
+      base::BindOnce(&FederatedAuthRequestImpl::ShowModalDialog,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     idp_info->metadata.idp_signin_url));
 }
 
 void FederatedAuthRequestImpl::CloseModalDialogView() {
   if (identity_registry_) {
-    identity_registry_->Notify(origin());
+    identity_registry_->NotifyClose(origin());
   }
 }
 
@@ -1553,6 +1553,14 @@
                            /*should_delay_callback=*/false);
 }
 
+void FederatedAuthRequestImpl::ShowModalDialog(const GURL& url) {
+  WebContents* web_contents = request_dialog_controller_->ShowModalDialog(
+      url, base::BindOnce(&FederatedAuthRequestImpl::OnDialogDismissed,
+                          weak_ptr_factory_.GetWeakPtr()));
+  IdentityRegistry::CreateForWebContents(web_contents, this,
+                                         url::Origin::Create(url));
+}
+
 void FederatedAuthRequestImpl::OnContinueOnResponseReceived(
     IdentityProviderConfigPtr idp,
     IdpNetworkRequestManager::FetchStatus status,
@@ -1566,13 +1574,7 @@
   }
 
   // TODO(crbug.com/1429083): record the appropriate metrics.
-  request_dialog_controller_->ShowModalDialog(
-      std::move(continue_on),
-      base::BindOnce(&FederatedAuthRequestImpl::CompleteTokenRequest,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(idp),
-                     std::move(status)),
-      base::BindOnce(&FederatedAuthRequestImpl::OnDialogDismissed,
-                     weak_ptr_factory_.GetWeakPtr()));
+  ShowModalDialog(idp->config_url);
 }
 
 void FederatedAuthRequestImpl::OnTokenResponseReceived(
@@ -1964,6 +1966,17 @@
   request_dialog_controller_->CloseModalDialog();
 }
 
+bool FederatedAuthRequestImpl::NotifyResolve(const std::string& token) {
+  // TODO(crbug.com/1429083): handle the multi-idp case when there are
+  // more than one config_urls hanging.
+  CompleteRequest(FederatedAuthRequestResult::kSuccess, TokenStatus::kSuccess,
+                  absl::nullopt, token,
+                  /*should_delay_callback=*/false);
+  // TODO(crbug.com/1429083): handle the corner cases where CompleteRequest
+  // can't actually fulfill the request.
+  return true;
+}
+
 void FederatedAuthRequestImpl::OnRejectRequest() {
   if (auth_request_token_callback_) {
     DCHECK(!logout_callback_);
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 33e4f5e..6730723 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -100,6 +100,7 @@
 
   // content::FederatedIdentityModalDialogViewDelegate:
   void NotifyClose() override;
+  bool NotifyResolve(const std::string& token) override;
 
   // Rejects the pending request if it has not been resolved naturally yet.
   void OnRejectRequest();
@@ -202,6 +203,7 @@
       bool should_delay_callback);
 
   void MaybeShowAccountsDialog();
+  void ShowModalDialog(const GURL& url);
 
   // Updates the IdpSigninStatus in case of accounts fetch failure and shows a
   // failure UI if applicable.
@@ -312,9 +314,6 @@
   bool RequiresUserMediation();
   void SetRequiresUserMediation(bool requires_user_mediation);
 
-  void CreateIdentityRegistry(const url::Origin& idp_origin,
-                              content::WebContents* web_contents);
-
   std::unique_ptr<IdpNetworkRequestManager> network_manager_;
   std::unique_ptr<IdentityRequestDialogController> request_dialog_controller_;
 
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index d509fcb9..b376f9b 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -611,7 +611,7 @@
       const std::string& idp_for_display,
       const IdentityProviderMetadata& idp_metadata,
       IdentityRequestDialogController::DismissCallback dismiss_callback,
-      IdentityRequestDialogController::IdentityRegistryCallback
+      IdentityRequestDialogController::SigninToIdPCallback
           identity_registry_callback) override {
     if (!state_) {
       return;
@@ -721,7 +721,9 @@
                                        delegate,
                                        registry_origin) {}
 
-  void Notify(const url::Origin& notifier_origin) override { notified_ = true; }
+  void NotifyClose(const url::Origin& notifier_origin) override {
+    notified_ = true;
+  }
 };
 
 }  // namespace
@@ -4021,13 +4023,19 @@
 
   // When the pop-up window is opened, resolve it immediately by
   // producing an access token.
-  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog(_, _, _))
-      .WillOnce(::testing::WithArg<1>(
-          [&](IdentityRequestDialogController::TokenCallback on_resolve) {
-            std::move(on_resolve).Run("an-access-token");
-          }));
+  std::unique_ptr<WebContents> modal(CreateTestWebContents());
+  auto impl = federated_auth_request_impl_;
+  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog(_, _))
+      .WillOnce(::testing::WithArg<0>([&modal, &impl](const GURL& url) {
+        impl->NotifyResolve("an-access-token");
+        return modal.get();
+      }));
 
-  RunAuthTest(parameters, kExpectationSuccess, config);
+  RequestExpectations success = {RequestTokenStatus::kSuccess,
+                                 {FederatedAuthRequestResult::kSuccess},
+                                 /*selected_idp_config_url=*/absl::nullopt};
+
+  RunAuthTest(parameters, success, config);
 
   // When the authorization is delegated and the feature is enabled
   // we don't fetch the client metadata endpoint (which is used to
diff --git a/content/browser/webid/identity_registry.cc b/content/browser/webid/identity_registry.cc
index a1e4e29a..7d684607 100644
--- a/content/browser/webid/identity_registry.cc
+++ b/content/browser/webid/identity_registry.cc
@@ -21,7 +21,7 @@
 
 IdentityRegistry::~IdentityRegistry() = default;
 
-void IdentityRegistry::Notify(const url::Origin& notifier_origin) {
+void IdentityRegistry::NotifyClose(const url::Origin& notifier_origin) {
   if (!registry_origin_.IsSameOriginWith(notifier_origin)) {
     return;
   }
@@ -29,6 +29,14 @@
   delegate_->NotifyClose();
 }
 
+bool IdentityRegistry::NotifyResolve(const url::Origin& notifier_origin,
+                                     const std::string& token) {
+  if (!registry_origin_.IsSameOriginWith(notifier_origin)) {
+    return false;
+  }
+  return delegate_->NotifyResolve(token);
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(IdentityRegistry);
 
 }  // namespace content
diff --git a/content/browser/webid/identity_registry.h b/content/browser/webid/identity_registry.h
index 3f6ead18..fb188033 100644
--- a/content/browser/webid/identity_registry.h
+++ b/content/browser/webid/identity_registry.h
@@ -21,7 +21,9 @@
     : public WebContentsUserData<IdentityRegistry> {
  public:
   ~IdentityRegistry() override;
-  virtual void Notify(const url::Origin& notifier_origin);
+  virtual void NotifyClose(const url::Origin& notifier_origin);
+  virtual bool NotifyResolve(const url::Origin& notifier_origin,
+                             const std::string& token);
 
  private:
   friend class content::WebContentsUserData<IdentityRegistry>;
diff --git a/content/browser/webid/identity_registry_unittest.cc b/content/browser/webid/identity_registry_unittest.cc
index 03689d6..6b0bd99 100644
--- a/content/browser/webid/identity_registry_unittest.cc
+++ b/content/browser/webid/identity_registry_unittest.cc
@@ -58,7 +58,7 @@
 // be closed.
 TEST_F(IdentityRegistryTest, NotifierAndRegistrySameOrigin) {
   EXPECT_FALSE(test_delegate_->closed_);
-  identity_registry_->Notify(url::Origin::Create(GURL(kIdpUrl)));
+  identity_registry_->NotifyClose(url::Origin::Create(GURL(kIdpUrl)));
   EXPECT_TRUE(test_delegate_->closed_);
 }
 
@@ -66,7 +66,7 @@
 // remain open.
 TEST_F(IdentityRegistryTest, NotifierAndRegistryCrossOrigin) {
   EXPECT_FALSE(test_delegate_->closed_);
-  identity_registry_->Notify(
+  identity_registry_->NotifyClose(
       url::Origin::Create(GURL("https://cross-origin.example")));
   EXPECT_FALSE(test_delegate_->closed_);
 }
diff --git a/content/browser/webid/test/mock_identity_request_dialog_controller.h b/content/browser/webid/test/mock_identity_request_dialog_controller.h
index 4d465c9d..8b4a5edf 100644
--- a/content/browser/webid/test/mock_identity_request_dialog_controller.h
+++ b/content/browser/webid/test/mock_identity_request_dialog_controller.h
@@ -40,9 +40,8 @@
                     const std::string&,
                     const content::IdentityProviderMetadata&,
                     DismissCallback,
-                    IdentityRegistryCallback));
-  MOCK_METHOD3(ShowModalDialog,
-               void(const GURL&, TokenCallback, DismissCallback));
+                    SigninToIdPCallback));
+  MOCK_METHOD2(ShowModalDialog, WebContents*(const GURL&, DismissCallback));
 };
 
 }  // namespace content
diff --git a/content/browser/webid/test/mock_modal_dialog_view_delegate.h b/content/browser/webid/test/mock_modal_dialog_view_delegate.h
index 01aa5e5..0e40138 100644
--- a/content/browser/webid/test/mock_modal_dialog_view_delegate.h
+++ b/content/browser/webid/test/mock_modal_dialog_view_delegate.h
@@ -23,6 +23,7 @@
       delete;
 
   MOCK_METHOD0(NotifyClose, void());
+  MOCK_METHOD1(NotifyResolve, bool(const std::string&));
 };
 
 }  // namespace content
diff --git a/content/browser/webid/test/webid_test_content_browser_client.h b/content/browser/webid/test/webid_test_content_browser_client.h
index be60df4..0d945b1 100644
--- a/content/browser/webid/test/webid_test_content_browser_client.h
+++ b/content/browser/webid/test/webid_test_content_browser_client.h
@@ -44,6 +44,11 @@
                            FederatedIdentityModalDialogViewDelegate* delegate,
                            const url::Origin& url);
 
+  IdentityRequestDialogController*
+  GetIdentityRequestDialogControllerForTests() {
+    return test_dialog_controller_.get();
+  }
+
   MDocProvider* GetMDocProviderForTests() { return test_mdoc_provider_.get(); }
 
  private:
diff --git a/content/browser/webid/webid_browsertest.cc b/content/browser/webid/webid_browsertest.cc
index 5aff2081..5d3c203 100644
--- a/content/browser/webid/webid_browsertest.cc
+++ b/content/browser/webid/webid_browsertest.cc
@@ -16,6 +16,7 @@
 #include "components/network_session_configurator/common/network_switches.h"
 #include "content/browser/webid/fake_identity_request_dialog_controller.h"
 #include "content/browser/webid/identity_registry.h"
+#include "content/browser/webid/test/mock_identity_request_dialog_controller.h"
 #include "content/browser/webid/test/mock_mdoc_provider.h"
 #include "content/browser/webid/test/mock_modal_dialog_view_delegate.h"
 #include "content/browser/webid/test/webid_test_content_browser_client.h"
@@ -780,9 +781,42 @@
 
   idp_server()->SetConfigResponseDetails(config_details);
 
+  auto mock = std::make_unique<
+      ::testing::NiceMock<MockIdentityRequestDialogController>>();
+  test_browser_client_->SetIdentityRequestDialogController(std::move(mock));
+
+  MockIdentityRequestDialogController* controller =
+      static_cast<MockIdentityRequestDialogController*>(
+          test_browser_client_->GetIdentityRequestDialogControllerForTests());
+
+  auto config_url = GURL(BaseIdpUrl());
+
+  // Expects the account chooser to be opened. Selects the first account.
+  EXPECT_CALL(*controller, ShowAccountsDialog(_, _, _, _, _, _, _, _))
+      .WillOnce(::testing::WithArg<6>([&config_url](auto on_selected) {
+        std::move(on_selected)
+            .Run(config_url,
+                 /* account_id=*/"not_real_account",
+                 /* is_sign_in= */ true);
+      }));
+
+  // Create a WebContents that represents the modal dialog, specifically
+  // the structure that the Identity Registry hangs to.
+  Shell* modal = CreateBrowser();
+  modal->LoadURL(config_url);
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(*controller, ShowModalDialog(_, _))
+      .WillOnce(::testing::WithArg<0>([&modal, &run_loop](const GURL& url) {
+        // When the pop-up window is opened, resolve it immediately by
+        // returning a test web contents, which can then later be used
+        // to refer to the identity registry.
+        run_loop.Quit();
+        return modal->web_contents();
+      }));
+
   std::string script = R"(
-        (async () => {
-          var x = (await navigator.credentials.get({
+          var result = navigator.credentials.get({
             identity: {
               providers: [{
                 configURL: ')" +
@@ -794,15 +828,27 @@
                 ],
               }]
             }
-          }));
-          return x.token;
-        }) ()
+         }).then(({token}) => token);
     )";
 
-  // Assert that a fake token was returned from the pop-up window,
-  // as opposed to directly from the id assertion endpoint.
-  EXPECT_EQ(std::string("--fake-token-from-pop-up-window--"),
-            EvalJs(shell(), script));
+  // Kick off the identity credential request and deliberately
+  // leave the promise hanging, since it requires UX permission
+  // prompts to be accepted later.
+  EXPECT_TRUE(content::ExecJs(shell(), script,
+                              content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
+
+  // Wait for the modal dialog to be resolved.
+  run_loop.Run();
+
+  std::string token = "--fake-token-from-pop-up-window--";
+
+  // Resolve the hanging token request by notifying the registry.
+  EXPECT_TRUE(content::ExecJs(
+      modal, R"(IdentityProvider.resolve(')" + token + R"('))"));
+
+  // Finally, wait for the promise to resolve and compare its result
+  // to the expected token that was provided in the modal dialog.
+  EXPECT_EQ(token, EvalJs(shell(), "result"));
 }
 
 }  // namespace content
diff --git a/content/browser/webrtc/webrtc_internals_browsertest.cc b/content/browser/webrtc/webrtc_internals_browsertest.cc
index 6e87663..08a77ff 100644
--- a/content/browser/webrtc/webrtc_internals_browsertest.cc
+++ b/content/browser/webrtc/webrtc_internals_browsertest.cc
@@ -167,7 +167,7 @@
 
  protected:
   bool ExecuteJavascript(const string& javascript) {
-    return ExecuteScript(shell(), javascript);
+    return ExecJs(shell(), javascript);
   }
 
   void ExpectTitle(const std::string& expected_title) const {
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/AccessibilityContentShellActivityTestRule.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/AccessibilityContentShellActivityTestRule.java
index 436e9b47..7149a55 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/AccessibilityContentShellActivityTestRule.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/AccessibilityContentShellActivityTestRule.java
@@ -98,7 +98,7 @@
     public void setupTestFramework() {
         mWcax = getWebContentsAccessibility();
         mWcax.setAccessibilityEnabledForTesting();
-        AccessibilityState.setAccessibilityEnabledForTesting(true);
+        AccessibilityState.setIsAnyAccessibilityServiceEnabledForTesting(true);
         AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_ALL);
 
         mNodeProvider = getAccessibilityNodeProvider();
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
index 6cdef29..3d3b1d2 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
@@ -393,8 +393,8 @@
         // Set the relevant features and accessibility state.
         FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            AccessibilityState.setScreenReaderEnabledForTesting(true);
-            AccessibilityState.setOnlyPasswordManagersEnabledForTesting(false);
+            AccessibilityState.setIsScreenReaderEnabledForTesting(true);
+            AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
         });
 
         var histogramWatcher =
@@ -428,9 +428,8 @@
         // Set the relevant features and accessibility state.
         FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            AccessibilityState.setScreenReaderEnabledForTesting(false);
-            AccessibilityState.setOnlyPasswordManagersEnabledForTesting(true);
-
+            AccessibilityState.setIsScreenReaderEnabledForTesting(false);
+            AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(true);
         });
 
         var histogramWatcher =
@@ -465,8 +464,8 @@
         // Set the relevant features and screen reader state.
         FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            AccessibilityState.setScreenReaderEnabledForTesting(false);
-            AccessibilityState.setOnlyPasswordManagersEnabledForTesting(false);
+            AccessibilityState.setIsScreenReaderEnabledForTesting(false);
+            AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
         });
 
         var histogramWatcher =
@@ -503,8 +502,8 @@
         FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_NONE);
-            AccessibilityState.setScreenReaderEnabledForTesting(true);
-            AccessibilityState.setOnlyPasswordManagersEnabledForTesting(false);
+            AccessibilityState.setIsScreenReaderEnabledForTesting(true);
+            AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
         });
 
         var histogramWatcher =
@@ -541,8 +540,8 @@
         FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_NONE);
-            AccessibilityState.setScreenReaderEnabledForTesting(false);
-            AccessibilityState.setOnlyPasswordManagersEnabledForTesting(true);
+            AccessibilityState.setIsScreenReaderEnabledForTesting(false);
+            AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(true);
         });
 
         var histogramWatcher =
@@ -579,8 +578,8 @@
         FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_NONE);
-            AccessibilityState.setScreenReaderEnabledForTesting(false);
-            AccessibilityState.setOnlyPasswordManagersEnabledForTesting(false);
+            AccessibilityState.setIsScreenReaderEnabledForTesting(false);
+            AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
         });
 
         var histogramWatcher =
diff --git a/content/public/browser/attribution_data_model.h b/content/public/browser/attribution_data_model.h
index a152e789..b1ef129e 100644
--- a/content/public/browser/attribution_data_model.h
+++ b/content/public/browser/attribution_data_model.h
@@ -5,7 +5,7 @@
 #ifndef CONTENT_PUBLIC_BROWSER_ATTRIBUTION_DATA_MODEL_H_
 #define CONTENT_PUBLIC_BROWSER_ATTRIBUTION_DATA_MODEL_H_
 
-#include <vector>
+#include <set>
 
 #include "base/functional/callback_forward.h"
 #include "content/common/content_export.h"
@@ -40,7 +40,7 @@
   virtual ~AttributionDataModel() = default;
 
   virtual void GetAllDataKeys(
-      base::OnceCallback<void(std::vector<DataKey>)> callback) = 0;
+      base::OnceCallback<void(std::set<DataKey>)> callback) = 0;
 
   virtual void RemoveAttributionDataByDataKey(const DataKey& data_key,
                                               base::OnceClosure callback) = 0;
@@ -48,4 +48,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_PUBLIC_BROWSER_ATTRIBUTION_DATA_MODEL_H_
\ No newline at end of file
+#endif  // CONTENT_PUBLIC_BROWSER_ATTRIBUTION_DATA_MODEL_H_
diff --git a/content/public/browser/federated_identity_modal_dialog_view_delegate.h b/content/public/browser/federated_identity_modal_dialog_view_delegate.h
index bf93692..41f86c0 100644
--- a/content/public/browser/federated_identity_modal_dialog_view_delegate.h
+++ b/content/public/browser/federated_identity_modal_dialog_view_delegate.h
@@ -5,6 +5,8 @@
 #ifndef CONTENT_PUBLIC_BROWSER_FEDERATED_IDENTITY_MODAL_DIALOG_VIEW_DELEGATE_H_
 #define CONTENT_PUBLIC_BROWSER_FEDERATED_IDENTITY_MODAL_DIALOG_VIEW_DELEGATE_H_
 
+#include <string>
+
 #include "content/common/content_export.h"
 
 namespace content {
@@ -22,6 +24,10 @@
 
   // Closes the FedCM modal dialog, if any.
   virtual void NotifyClose() = 0;
+
+  // The modal dialog has provided a token to resolve the original request.
+  // TODO(crbug.com/1429083): pass the configURL in the notification.
+  virtual bool NotifyResolve(const std::string& token) = 0;
 };
 
 }  // namespace content
diff --git a/content/public/browser/identity_request_dialog_controller.cc b/content/public/browser/identity_request_dialog_controller.cc
index 1062136..7de604e8b 100644
--- a/content/public/browser/identity_request_dialog_controller.cc
+++ b/content/public/browser/identity_request_dialog_controller.cc
@@ -74,7 +74,7 @@
     const std::string& idp_for_display,
     const IdentityProviderMetadata& idp_metadata,
     DismissCallback dismiss_callback,
-    IdentityRegistryCallback identity_registry_callback) {
+    SigninToIdPCallback signin_callback) {
   if (!is_interception_enabled_) {
     std::move(dismiss_callback).Run(DismissReason::kOther);
   }
@@ -96,13 +96,13 @@
   }
 }
 
-void IdentityRequestDialogController::ShowModalDialog(
+WebContents* IdentityRequestDialogController::ShowModalDialog(
     const GURL& url,
-    TokenCallback on_resolve,
     DismissCallback dismiss_callback) {
   if (!is_interception_enabled_) {
     std::move(dismiss_callback).Run(DismissReason::kOther);
   }
+  return nullptr;
 }
 
 void IdentityRequestDialogController::CloseModalDialog() {}
diff --git a/content/public/browser/identity_request_dialog_controller.h b/content/public/browser/identity_request_dialog_controller.h
index a1efd52..202e051 100644
--- a/content/public/browser/identity_request_dialog_controller.h
+++ b/content/public/browser/identity_request_dialog_controller.h
@@ -89,8 +89,7 @@
 
   using DismissCallback =
       base::OnceCallback<void(DismissReason dismiss_reason)>;
-  using IdentityRegistryCallback =
-      base::OnceCallback<void(WebContents* web_contents)>;
+  using SigninToIdPCallback = base::OnceCallback<void()>;
 
   IdentityRequestDialogController() = default;
 
@@ -135,7 +134,7 @@
       const std::string& idp_for_display,
       const IdentityProviderMetadata& idp_metadata,
       DismissCallback dismiss_callback,
-      IdentityRegistryCallback identity_registry_callback);
+      SigninToIdPCallback signin_callback);
 
   // Only to be called after a dialog is shown.
   virtual std::string GetTitle() const;
@@ -145,9 +144,8 @@
   virtual void ShowIdpSigninFailureDialog(base::OnceClosure dismiss_callback);
 
   // Show a modal dialog that loads content from the IdP.
-  virtual void ShowModalDialog(const GURL& url,
-                               TokenCallback on_resolve,
-                               DismissCallback dismiss_callback);
+  virtual WebContents* ShowModalDialog(const GURL& url,
+                                       DismissCallback dismiss_callback);
 
   // Closes the modal dialog.
   virtual void CloseModalDialog();
diff --git a/content/public/common/isolated_world_ids.h b/content/public/common/isolated_world_ids.h
index 73c7f0b..98e6f1f 100644
--- a/content/public/common/isolated_world_ids.h
+++ b/content/public/common/isolated_world_ids.h
@@ -8,8 +8,8 @@
 namespace content {
 
 enum IsolatedWorldIDs : int32_t {
-  // Chrome cannot use ID 0 for an isolated world because 0 represents the main
-  // world.
+  // The main world. Chrome cannot use ID 0 for an isolated world because 0
+  // represents the main world.
   ISOLATED_WORLD_ID_GLOBAL = 0,
   // Custom isolated world ids used by other embedders should start from here.
   ISOLATED_WORLD_ID_CONTENT_END,
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 4322c1e..07fc5d2 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -161,86 +161,6 @@
 namespace content {
 namespace {
 
-// Executes the passed |script| in the frame specified by |render_frame_host|.
-// If |result| is not NULL, stores the value that the evaluation of the script
-// in |result|.  Returns true on success.
-[[nodiscard]] bool ExecuteScriptHelper(RenderFrameHost* render_frame_host,
-                                       const std::string& script,
-                                       bool user_gesture,
-                                       int32_t world_id,
-                                       std::unique_ptr<base::Value>* result) {
-  // TODO(lukasza): Only get messages from the specific |render_frame_host|.
-  DOMMessageQueue dom_message_queue(render_frame_host);
-
-  std::u16string script16 = base::UTF8ToUTF16(script);
-  if (world_id == ISOLATED_WORLD_ID_GLOBAL && user_gesture) {
-    // Prerendering pages will never have user gesture.
-    CHECK(render_frame_host->GetLifecycleState() !=
-          RenderFrameHost::LifecycleState::kPrerendering);
-    render_frame_host->ExecuteJavaScriptWithUserGestureForTests(
-        script16, base::NullCallback(), world_id);
-  } else {
-    // Note that |user_gesture| here is ignored when the world is not main. We
-    // allow a value of |true| because it's the default, but in blink, the
-    // execution will occur with no user gesture.
-    render_frame_host->ExecuteJavaScriptForTests(script16, base::NullCallback(),
-                                                 world_id);
-  }
-
-  std::string json;
-  if (!dom_message_queue.WaitForMessage(&json)) {
-    DLOG(ERROR) << "Cannot communicate with DOMMessageQueue.";
-    return false;
-  }
-
-  // Nothing more to do for callers that ignore the returned JS value.
-  if (!result)
-    return true;
-
-  auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
-      json, base::JSON_ALLOW_TRAILING_COMMAS);
-  if (!parsed_json.has_value()) {
-    *result = nullptr;
-    DLOG(ERROR) << parsed_json.error().message;
-    return false;
-  }
-  *result = base::Value::ToUniquePtrValue(std::move(*parsed_json));
-
-  return true;
-}
-
-bool ExecuteScriptWithUserGestureControl(RenderFrameHost* frame,
-                                         const std::string& script,
-                                         bool user_gesture) {
-  // TODO(lukasza): ExecuteScript should just call
-  // ExecuteJavaScriptWithUserGestureForTests and avoid modifying the original
-  // script (and at that point we should merge it with and remove
-  // ExecuteScriptAsync).  This is difficult to change, because many tests
-  // depend on the message loop pumping done by ExecuteScriptHelper below (this
-  // is fragile - these tests should wait on a more specific thing instead).
-
-  // TODO(nick): This function can't be replaced with a call to ExecJs(), since
-  // ExecJs calls eval() which might be blocked by the page's CSP.
-  std::string expected_response =
-      "ExecuteScript-" + base::Uuid::GenerateRandomV4().AsLowercaseString();
-  std::string new_script = base::StringPrintf(
-      R"( %s;  // Original script.
-          window.domAutomationController.send('%s'); )",
-      script.c_str(), expected_response.c_str());
-
-  std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptHelper(frame, new_script, user_gesture,
-                           ISOLATED_WORLD_ID_GLOBAL, &value) ||
-      !value.get()) {
-    return false;
-  }
-
-  DCHECK_EQ(base::Value::Type::STRING, value->type());
-  if (value->is_string())
-    DCHECK_EQ(expected_response, value->GetString());
-  return true;
-}
-
 void BuildSimpleWebKeyEvent(blink::WebInputEvent::Type type,
                             ui::DomKey key,
                             ui::DomCode code,
@@ -1439,15 +1359,6 @@
   return render_frame_host;
 }
 
-bool ExecuteScript(const ToRenderFrameHost& adapter,
-                   const std::string& script) {
-  // Prerendering pages will never have user gesture.
-  bool user_gesture = adapter.render_frame_host()->GetLifecycleState() !=
-                      RenderFrameHost::LifecycleState::kPrerendering;
-  return ExecuteScriptWithUserGestureControl(adapter.render_frame_host(),
-                                             script, user_gesture);
-}
-
 void ExecuteScriptAsync(const ToRenderFrameHost& adapter,
                         const std::string& script) {
   // Prerendering pages will never have user gesture.
@@ -1533,7 +1444,7 @@
 // Parse a JS stack trace out of |js_error|, detect frames that match
 // |source_name|, and interleave the appropriate lines of source code from
 // |source| into the error report. This is meant to be useful for scripts that
-// are passed to ExecuteScript functions, and hence dynamically generated.
+// are passed to ExecJs/EvalJs functions, and hence dynamically generated.
 //
 // An adjustment of |column_adjustment_for_line_one| characters is subtracted
 // when mapping positions from line 1 of |source|. This is to offset the effect
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index d20f18b..f3cb642 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -241,7 +241,7 @@
 //
 // This method does not trigger a user activation before the navigation.  If
 // necessary, a user activation can be triggered right before calling this
-// method, e.g. by calling |ExecuteScript(frame_tree_node, "")|.
+// method, e.g. by calling |ExecJs(frame_tree_node, "")|.
 bool NavigateIframeToURL(WebContents* web_contents,
                          const std::string& iframe_id,
                          const GURL& url);
@@ -538,7 +538,7 @@
 // Method to check what devices we have on the system.
 bool IsWebcamAvailableOnSystem(WebContents* web_contents);
 
-// Allow ExecuteScript* methods to target either a WebContents or a
+// Allow ExecJs/EvalJs methods to target either a WebContents or a
 // RenderFrameHost.  Targeting a WebContents means executing the script in the
 // RenderFrameHost returned by WebContents::GetPrimaryMainFrame(), which is the
 // main frame.  Pass a specific RenderFrameHost to target it. Embedders may
@@ -563,35 +563,13 @@
 RenderFrameHost* ConvertToRenderFrameHost(RenderFrameHost* render_view_host);
 RenderFrameHost* ConvertToRenderFrameHost(WebContents* web_contents);
 
-// Deprecated: in new code, prefer ExecJs() -- it works the same, but has
-// better error handling. (Note: still use ExecuteScript() on pages with a
-// Content Security Policy).
-//
-// Executes the passed |script| in the specified frame with the user gesture.
-//
-// Appends |domAutomationController.send(...)| to the end of |script| and waits
-// until the response comes back (pumping the message loop while waiting).  The
-// |script| itself should not invoke domAutomationController.send().
-//
-// Returns true on success (if the renderer responded back with the expected
-// value).  Returns false otherwise (e.g. if the script threw an exception
-// before calling the appended |domAutomationController.send(...)|, or if the
-// renderer died or if the renderer called |domAutomationController.send(...)|
-// with a malformed or unexpected value).
+// Executes the passed |script| in the specified frame with a user gesture,
+// without waiting for |script| completion.
 //
 // See also:
-// - ExecJs (preferred replacement with better error handling)
+// - ExecJs (if you want to wait for script completion, or detect syntax errors)
 // - EvalJs (if you want to retrieve a value)
-// - ExecuteScriptAsync (if you don't want to block for |script| completion)
 // - DOMMessageQueue (to manually wait for domAutomationController.send(...))
-[[nodiscard]] bool ExecuteScript(const ToRenderFrameHost& adapter,
-                                 const std::string& script);
-
-// Similar to ExecuteScript above, but
-// - Doesn't modify the |script|.
-// - Kicks off execution of the |script| in the specified frame and returns
-//   immediately (without waiting for a response from the renderer and/or
-//   without checking that the script succeeded).
 void ExecuteScriptAsync(const ToRenderFrameHost& adapter,
                         const std::string& script);
 
@@ -660,7 +638,7 @@
 // Example 1:
 //
 //   GURL page_url("http://example.com");
-//   EXPECT_TRUE(ExecuteScript(
+//   EXPECT_TRUE(ExecJs(
 //       shell(), JsReplace("window.open($1, '_blank');", page_url)));
 //
 // $1 is replaced with a double-quoted JS string literal:
@@ -669,7 +647,7 @@
 // Example 2:
 //
 //   bool forced_reload = true;
-//   EXPECT_TRUE(ExecuteScript(
+//   EXPECT_TRUE(ExecJs(
 //       shell(), JsReplace("window.location.reload($1);", forced_reload)));
 //
 // This becomes "window.location.reload(true);" -- because bool values are
@@ -712,7 +690,7 @@
   const base::Value value;  // Value; if things went well.
   const std::string error;  // Error; if things went badly.
 
-  // Creates an ExecuteScript result. If |error| is non-empty, |value| will be
+  // Creates an EvalJs result. If |error| is non-empty, |value| will be
   // ignored.
   EvalJsResult(base::Value value, const std::string& error);
 
@@ -900,11 +878,7 @@
 // exception.
 //
 // As with EvalJs(), if the script passed evaluates to a Promise, this waits
-// until it resolves.
-//
-// Unlike ExecuteScript(), this catches syntax errors and uncaught exceptions,
-// and gives more useful error messages when things go wrong. Prefer ExecJs to
-// ExecuteScript(), unless your page has a CSP.
+// until it resolves (by default).
 [[nodiscard]] ::testing::AssertionResult ExecJs(
     const ToRenderFrameHost& execution_target,
     const std::string& script,
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 07e74afd..a1e6aea 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -1694,6 +1694,7 @@
   if (!serialize_post_lifecycle_) {
     legacy_event_schedule_status_ = LegacyEventScheduleStatus::kNotWaiting;
   }
+  serialization_in_flight_ = false;
 }
 
 void RenderAccessibilityImpl::MaybeSendUKM() {
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.h b/content/renderer/media/renderer_webaudiodevice_impl.h
index 31a29c3..7b212bc 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.h
+++ b/content/renderer/media/renderer_webaudiodevice_impl.h
@@ -10,6 +10,7 @@
 #include <memory>
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/threading/thread_checker.h"
 #include "content/common/content_export.h"
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index b19bb01f..6975558 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -6267,7 +6267,8 @@
     auto pip_mojom_opts = blink::mojom::PictureInPictureWindowOptions::New();
     pip_mojom_opts->width = pip_options->width;
     pip_mojom_opts->height = pip_options->height;
-    pip_mojom_opts->initial_aspect_ratio = pip_options->initial_aspect_ratio;
+    // TODO(crbug.com/1444658): Remove this from mojom and the browser side.
+    pip_mojom_opts->initial_aspect_ratio = 0.0;
     // TODO(crbug.com/1410379): Remove this from mojom and the browser side.
     pip_mojom_opts->lock_aspect_ratio = false;
     params->pip_options = std::move(pip_mojom_opts);
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index e766c0b..3cf9b4f 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -506,6 +506,7 @@
     base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
         update_priority_signals_overrides,
     PrivateAggregationRequests pa_requests,
+    mojom::RejectReason reject_reason,
     std::vector<std::string> error_msgs)
     : context_recycler_for_rerun(std::move(context_recycler_for_rerun)),
       bid(std::move(bid)),
@@ -516,6 +517,7 @@
       update_priority_signals_overrides(
           std::move(update_priority_signals_overrides)),
       pa_requests(std::move(pa_requests)),
+      reject_reason(reject_reason),
       error_msgs(std::move(error_msgs)) {}
 
 BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult(
@@ -848,7 +850,7 @@
                      std::move(result->pa_requests),
                      std::move(result->non_kanon_pa_requests),
                      /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
-                     std::move(result->error_msgs)));
+                     result->reject_reason, std::move(result->error_msgs)));
 }
 
 absl::optional<BidderWorklet::V8State::SingleGenerateBidResult>
@@ -939,7 +941,9 @@
           /*debug_win_report_url=*/absl::nullopt,
           /*set_priority=*/absl::nullopt,
           /*update_priority_signals_overrides=*/{},
-          /*pa_requests=*/{}, std::move(errors_out)));
+          /*pa_requests=*/{},
+          /*reject_reason=*/mojom::RejectReason::kNotAvailable,
+          std::move(errors_out)));
     }
 
     context_recycler = fresh_context_recycler.get();
@@ -1168,6 +1172,7 @@
             ->TakeSetPrioritySignalsOverrides(),
         context_recycler->private_aggregation_bindings()
             ->TakePrivateAggregationRequests(),
+        context_recycler->set_bid_bindings()->reject_reason(),
         std::move(errors_out)));
   }
 
@@ -1185,7 +1190,7 @@
           ->TakeSetPrioritySignalsOverrides(),
       context_recycler->private_aggregation_bindings()
           ->TakePrivateAggregationRequests(),
-      std::move(errors_out)));
+      mojom::RejectReason::kNotAvailable, std::move(errors_out)));
 }
 
 std::unique_ptr<ContextRecycler>
@@ -1314,7 +1319,9 @@
           base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>(),
           /*pa_requests=*/
           PrivateAggregationRequests(), std::move(non_kanon_pa_requests),
-          bidding_latency, std::move(error_msgs)));
+          bidding_latency,
+          /*reject_reason=*/mojom::RejectReason::kNotAvailable,
+          std::move(error_msgs)));
 }
 
 void BidderWorklet::ResumeIfPaused() {
@@ -1764,6 +1771,7 @@
     PrivateAggregationRequests pa_requests,
     PrivateAggregationRequests non_kanon_pa_requests,
     base::TimeDelta bidding_latency,
+    mojom::RejectReason reject_reason,
     std::vector<std::string> error_msgs) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
 
@@ -1787,7 +1795,7 @@
           NullOptIfZero(task->wait_direct_from_seller_signals),
           /*trusted_bidding_signals_latency=*/
           NullOptIfZero(task->wait_trusted_signals)),
-      error_msgs);
+      reject_reason, error_msgs);
   CleanUpBidTaskOnUserThread(task);
 }
 
diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h
index da15f962..09575e1 100644
--- a/content/services/auction_worklet/bidder_worklet.h
+++ b/content/services/auction_worklet/bidder_worklet.h
@@ -330,6 +330,7 @@
         PrivateAggregationRequests pa_requests,
         PrivateAggregationRequests non_kanon_pa_requests,
         base::TimeDelta bidding_latency,
+        mojom::RejectReason reject_reason,
         std::vector<std::string> error_msgs)>;
     using ReportWinCallbackInternal =
         base::OnceCallback<void(absl::optional<GURL> report_url,
@@ -351,6 +352,7 @@
           base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
               update_priority_signals_overrides,
           PrivateAggregationRequests pa_requests,
+          mojom::RejectReason reject_reason,
           std::vector<std::string> error_msgs);
 
       SingleGenerateBidResult(const SingleGenerateBidResult&) = delete;
@@ -374,6 +376,7 @@
           update_priority_signals_overrides;
       PrivateAggregationRequests pa_requests;
       PrivateAggregationRequests non_kanon_pa_requests;
+      mojom::RejectReason reject_reason;
       std::vector<std::string> error_msgs;
     };
 
@@ -592,6 +595,7 @@
       PrivateAggregationRequests pa_requests,
       PrivateAggregationRequests non_kanon_pa_requests,
       base::TimeDelta bidding_latency,
+      mojom::RejectReason reject_reason,
       std::vector<std::string> error_msgs);
 
   // Removes `task` from `generate_bid_tasks_` only. Used in case where the
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index 8340fba..c330cc8b 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -147,6 +147,7 @@
       base::TimeDelta bidding_latency,
       mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
+      mojom::RejectReason reject_reason,
       const std::vector<std::string>& errors)>;
 
   explicit GenerateBidClientWithCallbacks(
@@ -199,6 +200,7 @@
            base::TimeDelta bidding_latency,
            mojom::GenerateBidDependencyLatenciesPtr
                generate_bid_dependency_latencies,
+           mojom::RejectReason reject_reason,
            const std::vector<std::string>& errors) {
           ADD_FAILURE() << "OnGenerateBidComplete should not be invoked.";
         });
@@ -240,6 +242,7 @@
       base::TimeDelta bidding_latency,
       mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
+      mojom::RejectReason reject_reason,
       const std::vector<std::string>& errors) override {
     // OnBiddingSignalsReceived() must be called first.
     EXPECT_TRUE(on_bidding_signals_received_invoked_);
@@ -251,7 +254,7 @@
              std::move(update_priority_signals_overrides),
              std::move(pa_requests), std::move(non_kanon_pa_requests),
              bidding_latency, std::move(generate_bid_dependency_latencies),
-             errors);
+             reject_reason, errors);
   }
 
  private:
@@ -741,6 +744,7 @@
       base::TimeDelta bidding_latency,
       mojom::GenerateBidDependencyLatenciesPtr
           generate_bid_dependency_latencies,
+      mojom::RejectReason reject_reason,
       const std::vector<std::string>& errors) {
     absl::optional<uint32_t> maybe_data_version;
     if (has_data_version) {
@@ -770,6 +774,11 @@
     non_kanon_pa_requests_ = std::move(non_kanon_pa_requests);
     generate_bid_dependency_latencies_ =
         std::move(generate_bid_dependency_latencies);
+    reject_reason_ = reject_reason;
+    if (bid_) {
+      // Shouldn't have a reject reason if the bid isn't rejected.
+      EXPECT_EQ(reject_reason_, mojom::RejectReason::kNotAvailable);
+    }
     bid_errors_ = errors;
     load_script_run_loop_->Quit();
   }
@@ -903,6 +912,7 @@
   PrivateAggregationRequests pa_requests_;
   PrivateAggregationRequests non_kanon_pa_requests_;
   mojom::GenerateBidDependencyLatenciesPtr generate_bid_dependency_latencies_;
+  mojom::RejectReason reject_reason_ = mojom::RejectReason::kNotAvailable;
   std::vector<std::string> bid_errors_;
 
   network::TestURLLoaderFactory url_loader_factory_;
@@ -1173,6 +1183,7 @@
           blink::AdDescriptor(GURL("https://response.test/")),
           /*ad_component_descriptors=*/absl::nullopt,
           /*modeling_signals=*/absl::nullopt, base::TimeDelta()));
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kNotAvailable);
 
   // Not specifying currency explicitly results in nullopt tag.
   RunGenerateBidWithReturnValueExpectingResult(
@@ -1184,6 +1195,7 @@
           blink::AdDescriptor(GURL("https://response.test/")),
           /*ad_component_descriptors=*/absl::nullopt,
           /*modeling_signals=*/absl::nullopt, base::TimeDelta()));
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kNotAvailable);
 
   // Trying to explicitly specify kUnspecifiedAdCurrency fails.
   RunGenerateBidWithReturnValueExpectingResult(
@@ -1195,6 +1207,7 @@
       /*expected_data_version=*/absl::nullopt,
       {"https://url.test/ generateBid() bidCurrency of '???' "
        "is not a currency code."});
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kWrongGenerateBidCurrency);
 
   // Expect currency codes to be 3 characters.
   RunGenerateBidWithReturnValueExpectingResult(
@@ -1204,6 +1217,7 @@
       /*expected_data_version=*/absl::nullopt,
       {"https://url.test/ generateBid() bidCurrency of 'USSD' "
        "is not a currency code."});
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kWrongGenerateBidCurrency);
 
   // Expect currency code to be 3 uppercase characters.
   RunGenerateBidWithReturnValueExpectingResult(
@@ -1213,6 +1227,7 @@
       /*expected_data_version=*/absl::nullopt,
       {"https://url.test/ generateBid() bidCurrency of 'usd' "
        "is not a currency code."});
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kWrongGenerateBidCurrency);
 }
 
 TEST_F(BidderWorkletTest, GenerateBidReturnBidCurrencyExpectCAD) {
@@ -1230,6 +1245,7 @@
           blink::AdDescriptor(GURL("https://response.test/")),
           /*ad_component_descriptors=*/absl::nullopt,
           /*modeling_signals=*/absl::nullopt, base::TimeDelta()));
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kNotAvailable);
 
   // Explicitly specified incorrect one.
   RunGenerateBidWithReturnValueExpectingResult(
@@ -1239,6 +1255,7 @@
       /*expected_data_version=*/absl::nullopt,
       {"https://url.test/ generateBid() bidCurrency mismatch; "
        "returned 'USD', expected 'CAD'."});
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kWrongGenerateBidCurrency);
 
   // Not specifying currency explicitly results in absl::nullopt, which matches
   // for compatibility reasons.
@@ -1251,6 +1268,7 @@
           blink::AdDescriptor(GURL("https://response.test/")),
           /*ad_component_descriptors=*/absl::nullopt,
           /*modeling_signals=*/absl::nullopt, base::TimeDelta()));
+  EXPECT_EQ(reject_reason_, mojom::RejectReason::kNotAvailable);
 }
 
 TEST_F(BidderWorkletTest, GenerateBidReturnValueUrl) {
@@ -2381,6 +2399,7 @@
                   base::TimeDelta bidding_latency,
                   mojom::GenerateBidDependencyLatenciesPtr
                       generate_bid_dependency_latencies,
+                  mojom::RejectReason reject_reason,
                   const std::vector<std::string>& errors) {
                 EXPECT_EQ(bid_value, bid->bid);
                 EXPECT_EQ(base::NumberToString(bid_value), bid->ad);
@@ -2495,6 +2514,7 @@
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
+                mojom::RejectReason reject_reason,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -2618,6 +2638,7 @@
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
+                mojom::RejectReason reject_reason,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -2747,6 +2768,7 @@
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
+                mojom::RejectReason reject_reason,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -2855,6 +2877,7 @@
                 base::TimeDelta bidding_latency,
                 mojom::GenerateBidDependencyLatenciesPtr
                     generate_bid_dependency_latencies,
+                mojom::RejectReason reject_reason,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
diff --git a/content/services/auction_worklet/context_recycler_unittest.cc b/content/services/auction_worklet/context_recycler_unittest.cc
index 144734c..7f11270 100644
--- a/content/services/auction_worklet/context_recycler_unittest.cc
+++ b/content/services/auction_worklet/context_recycler_unittest.cc
@@ -343,6 +343,8 @@
     EXPECT_EQ("https://example.com/ad1", bid->ad_descriptor.url);
     EXPECT_EQ(10.0, bid->bid);
     EXPECT_EQ(base::Milliseconds(500), bid->bid_duration);
+    EXPECT_EQ(mojom::RejectReason::kNotAvailable,
+              context_recycler.set_bid_bindings()->reject_reason());
   }
 
   {
@@ -377,6 +379,8 @@
                     "bid render URL 'https://example.com/ad1' isn't one of "
                     "the registered creative URLs."));
     EXPECT_FALSE(context_recycler.set_bid_bindings()->has_bid());
+    EXPECT_EQ(mojom::RejectReason::kNotAvailable,
+              context_recycler.set_bid_bindings()->reject_reason());
   }
 
   {
@@ -622,6 +626,8 @@
     EXPECT_EQ(10.0, bid->bid);
     ASSERT_TRUE(bid->bid_currency.has_value());
     EXPECT_EQ("USD", bid->bid_currency->currency_code());
+    EXPECT_EQ(mojom::RejectReason::kNotAvailable,
+              context_recycler.set_bid_bindings()->reject_reason());
   }
 
   {
@@ -655,6 +661,45 @@
             "https://example.org/script.js:3 Uncaught TypeError: bidCurrency "
             "mismatch; returned 'USD', expected 'CAD'."));
     EXPECT_FALSE(context_recycler.set_bid_bindings()->has_bid());
+    EXPECT_EQ(mojom::RejectReason::kWrongGenerateBidCurrency,
+              context_recycler.set_bid_bindings()->reject_reason());
+  }
+
+  {
+    // Make sure the reject reason doesn't latch.
+    ContextRecyclerScope scope(context_recycler);
+    mojom::BidderWorkletNonSharedParamsPtr params =
+        mojom::BidderWorkletNonSharedParams::New();
+    params->ads.emplace();
+    params->ads.value().emplace_back(GURL("https://example.com/ad2"),
+                                     absl::nullopt);
+
+    context_recycler.set_bid_bindings()->ReInitialize(
+        base::TimeTicks::Now(),
+        /*has_top_level_seller_origin=*/false, params.get(),
+        blink::AdCurrency::From("CAD"),
+        /*is_ad_excluded=*/matches_ad1,
+        /*is_component_ad_excluded=*/matches_ad1);
+
+    gin::Dictionary bid_dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    bid_dict.Set("render", std::string("https://example.com/ad2"));
+    bid_dict.Set("bid", 10.0);
+    bid_dict.Set("bidCurrency", std::string("CAD"));
+
+    std::vector<std::string> error_msgs;
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), bid_dict));
+
+    EXPECT_THAT(error_msgs, ElementsAre());
+    ASSERT_TRUE(context_recycler.set_bid_bindings()->has_bid());
+    mojom::BidderWorkletBidPtr bid =
+        context_recycler.set_bid_bindings()->TakeBid();
+    EXPECT_EQ("https://example.com/ad2", bid->ad_descriptor.url);
+    EXPECT_EQ(10.0, bid->bid);
+    ASSERT_TRUE(bid->bid_currency.has_value());
+    EXPECT_EQ("CAD", bid->bid_currency->currency_code());
+    EXPECT_EQ(mojom::RejectReason::kNotAvailable,
+              context_recycler.set_bid_bindings()->reject_reason());
   }
 }
 
diff --git a/content/services/auction_worklet/public/mojom/BUILD.gn b/content/services/auction_worklet/public/mojom/BUILD.gn
index 7051f63..d51866c 100644
--- a/content/services/auction_worklet/public/mojom/BUILD.gn
+++ b/content/services/auction_worklet/public/mojom/BUILD.gn
@@ -19,6 +19,7 @@
     "auction_worklet_service.mojom",
     "bidder_worklet.mojom",
     "private_aggregation_request.mojom",
+    "reject_reason.mojom",
     "seller_worklet.mojom",
   ]
   deps = [
diff --git a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
index 623bf34..b12a40e 100644
--- a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
+++ b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
@@ -5,6 +5,7 @@
 module auction_worklet.mojom;
 
 import "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom";
+import "content/services/auction_worklet/public/mojom/reject_reason.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "third_party/blink/public/mojom/devtools/devtools_agent.mojom";
@@ -263,6 +264,11 @@
   // `generate_bid_dependency_latencies` The amount of time GenerateBid had to
   // wait for each of its dependencies to be fulfilled before it could be run.
   //
+  // `reject_reason` If there is a valid bid that can participate in the auction
+  // this is kNotAvailable. Otherwise, it's the reason this bid was not
+  // permitted to participate (only kWrongGenerateBidCurrency at this time),
+  // or kNotAvailable.
+  //
   // `errors` The various error messages to be used for debugging. These are too
   //  sensitive for the renderer to see. There may be errors even when a bid
   //  is offered, and there may be no errors when there's no bid. Includes
@@ -281,6 +287,7 @@
       array<PrivateAggregationRequest> non_kanon_pa_requests,
       mojo_base.mojom.TimeDelta bidding_latency,
       GenerateBidDependencyLatencies generate_bid_dependency_latencies,
+      RejectReason reject_reason,
       array<string> errors);
 };
 
diff --git a/content/services/auction_worklet/public/mojom/reject_reason.mojom b/content/services/auction_worklet/public/mojom/reject_reason.mojom
new file mode 100644
index 0000000..74ad9025
--- /dev/null
+++ b/content/services/auction_worklet/public/mojom/reject_reason.mojom
@@ -0,0 +1,20 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module auction_worklet.mojom;
+
+// The reason a bid was rejected by an auction.
+enum RejectReason {
+  kNotAvailable = 0,
+  kInvalidBid = 1,
+  kBidBelowAuctionFloor = 2,
+  kPendingApprovalByExchange = 3,
+  kDisapprovedByExchange = 4,
+  kBlockedByPublisher = 5,
+  kLanguageExclusions = 6,
+  kCategoryExclusions = 7,
+  kBelowKAnonThreshold = 8,
+  kWrongGenerateBidCurrency = 9,
+  kWrongScoreAdCurrency = 10,
+};
diff --git a/content/services/auction_worklet/public/mojom/seller_worklet.mojom b/content/services/auction_worklet/public/mojom/seller_worklet.mojom
index b59885c..cf074e2c 100644
--- a/content/services/auction_worklet/public/mojom/seller_worklet.mojom
+++ b/content/services/auction_worklet/public/mojom/seller_worklet.mojom
@@ -5,6 +5,7 @@
 module auction_worklet.mojom;
 
 import "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom";
+import "content/services/auction_worklet/public/mojom/reject_reason.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "third_party/blink/public/mojom/devtools/devtools_agent.mojom";
@@ -55,19 +56,6 @@
   bool has_modified_bid;
 };
 
-// The reason a bid was rejected by an auction.
-enum RejectReason {
-  kNotAvailable = 0,
-  kInvalidBid = 1,
-  kBidBelowAuctionFloor = 2,
-  kPendingApprovalByExchange = 3,
-  kDisapprovedByExchange = 4,
-  kBlockedByPublisher = 5,
-  kLanguageExclusions = 6,
-  kCategoryExclusions = 7,
-  kBelowKAnonThreshold = 8,
-};
-
 // Interface for returning ScoreAd results. The advantage of having an interface
 // is that it makes ScoreAd() calls cancellable, and allows callbacks passed
 // over the Mojo pipe to be deleted when the Mojo pipe is, to avoid setting off
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index f8b7f3ce..11dc844 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -1096,6 +1096,11 @@
         }
         if (drop_for_invalid_currency) {
           score = 0;
+          // If scoreAd() didn't already specify a reject reason, note the
+          // currency mismatch.
+          if (reject_reason == mojom::RejectReason::kNotAvailable) {
+            reject_reason = mojom::RejectReason::kWrongScoreAdCurrency;
+          }
         }
       }
     }
@@ -1142,6 +1147,10 @@
     return;
   }
 
+  // This bid got accepted by scoreAd(), so clear any reject reason it may have
+  // set.
+  reject_reason = mojom::RejectReason::kNotAvailable;
+
   // If this is a component auction that modified the bid, validate the bid. Do
   // this after checking the score to avoid validating modified bid values from
   // reporting errors when desirability is <= 0.
@@ -1162,7 +1171,6 @@
     // This is a component auction that did not modify the bid; e.g. it's using
     // the bidder's bid as its own. Therefore, check it against our own
     // currency requirements.
-    // TODO(morlovich): One of the spots we want a new reject reason.
     if (!VerifySellerCurrency(
             /*provided_currency=*/bid_currency,
             /*expected_seller_currency=*/
@@ -1170,12 +1178,12 @@
             /*component_expect_bid_currency=*/component_expect_bid_currency,
             decision_logic_url_, "bid passthrough", errors_out)) {
       score = 0;
+      reject_reason = mojom::RejectReason::kWrongScoreAdCurrency;
     }
   }
 
   PostScoreAdCallbackToUserThread(
-      std::move(callback), score,
-      /*reject_reason=*/mojom::RejectReason::kNotAvailable,
+      std::move(callback), score, reject_reason,
       std::move(component_auction_modified_bid_params), bid_in_seller_currency,
       scoring_signals_data_version,
       context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(),
diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc
index 9191a22..724f7fc93 100644
--- a/content/services/auction_worklet/seller_worklet_unittest.cc
+++ b/content/services/auction_worklet/seller_worklet_unittest.cc
@@ -1145,7 +1145,27 @@
   RunScoreAdWithReturnValueExpectingResult(
       "{ad:null, desirability:1, allowComponentAuction:true, "
       "bid:1.2, bidCurrency: 'USSD'}",
-      0, {"https://url.test/ scoreAd() returned an invalid bidCurrency."});
+      0, {"https://url.test/ scoreAd() returned an invalid bidCurrency."},
+      /*expected_component_auction_modified_bid_params=*/
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/absl::nullopt,
+      /*expected_debug_loss_report_url=*/absl::nullopt,
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      mojom::RejectReason::kWrongScoreAdCurrency);
+
+  // Special case: if there is a manually specified reject-reason, it goes in
+  // and not the currency mismatch.
+  RunScoreAdWithReturnValueExpectingResult(
+      "{ad:null, desirability:1, allowComponentAuction:true, "
+      "bid:1.2, bidCurrency: 'USSD', rejectReason: 'category-exclusions'}",
+      0, {"https://url.test/ scoreAd() returned an invalid bidCurrency."},
+      /*expected_component_auction_modified_bid_params=*/
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/absl::nullopt,
+      /*expected_debug_loss_report_url=*/absl::nullopt,
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      mojom::RejectReason::kCategoryExclusions);
+
   auction_ad_config_non_shared_params_.seller_currency =
       blink::AdCurrency::From("CAD");
   RunScoreAdWithReturnValueExpectingResult(
@@ -1153,7 +1173,14 @@
       "bid:1.2, bidCurrency: 'USD'}",
       0,
       {"https://url.test/ scoreAd() bidCurrency mismatch vs own sellerCurrency,"
-       " expected 'CAD' got 'USD'."});
+       " expected 'CAD' got 'USD'."},
+      /*expected_component_auction_modified_bid_params=*/
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/absl::nullopt,
+      /*expected_debug_loss_report_url=*/absl::nullopt,
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      mojom::RejectReason::kWrongScoreAdCurrency);
+
   RunScoreAdWithReturnValueExpectingResult(
       "{ad:null, desirability:1, allowComponentAuction:true, "
       "bid:1.2, bidCurrency: 'CAD'}",
@@ -1173,7 +1200,13 @@
       "bid:1.2, bidCurrency: 'USD'}",
       0,
       {"https://url.test/ scoreAd() bidCurrency mismatch in component auction "
-       "vs parent auction bidderCurrency, expected 'EUR' got 'USD'."});
+       "vs parent auction bidderCurrency, expected 'EUR' got 'USD'."},
+      /*expected_component_auction_modified_bid_params=*/
+      mojom::ComponentAuctionModifiedBidParamsPtr(),
+      /*expected_data_version=*/absl::nullopt,
+      /*expected_debug_loss_report_url=*/absl::nullopt,
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      mojom::RejectReason::kWrongScoreAdCurrency);
   RunScoreAdWithReturnValueExpectingResult(
       "{ad:null, desirability:1, allowComponentAuction:true, "
       "bid:1.2, bidCurrency: 'EUR'}",
@@ -1191,6 +1224,29 @@
       "{ad:null, desirability:0, allowComponentAuction:true, bid:0}", 0);
 }
 
+// Test currency checks when score ad does not modify bid.
+TEST_F(SellerWorkletTest, ScoreAdDoesNotModifyBidCurrency) {
+  // Set us up as component seller.
+  browser_signals_other_seller_ =
+      mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
+          url::Origin::Create(GURL("https://top.level.seller.test")));
+  bid_currency_ = blink::AdCurrency::From("CAD");
+  auction_ad_config_non_shared_params_.seller_currency =
+      blink::AdCurrency::From("USD");
+  RunScoreAdWithReturnValueExpectingResult(
+      "{ad:null, desirability:1, allowComponentAuction:true}", 0,
+      {"https://url.test/ scoreAd() bid passthrough mismatch vs own "
+       "sellerCurrency, expected 'USD' got 'CAD'."},
+      /*expected_component_auction_modified_bid_params=*/
+      mojom::ComponentAuctionModifiedBidParams::New(
+          /*ad=*/"null", /*bid=*/0, /*bid_currency=*/absl::nullopt,
+          /*has_bid=*/false),
+      /*expected_data_version=*/absl::nullopt,
+      /*expected_debug_loss_report_url=*/absl::nullopt,
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      mojom::RejectReason::kWrongScoreAdCurrency);
+}
+
 // Test the `incomingBidInSellerCurrency` output field of scoreAd()
 TEST_F(SellerWorkletTest, ScoreAdIncomingBidInSellerCurrency) {
   // Configure bid currency to make sure checks for passthrough are done.
diff --git a/content/services/auction_worklet/set_bid_bindings.cc b/content/services/auction_worklet/set_bid_bindings.cc
index a8bd862..9121dd61 100644
--- a/content/services/auction_worklet/set_bid_bindings.cc
+++ b/content/services/auction_worklet/set_bid_bindings.cc
@@ -171,6 +171,7 @@
   bid_.reset();
   // Make sure we don't keep any dangling references to auction input.
   bidder_worklet_non_shared_params_ = nullptr;
+  reject_reason_ = mojom::RejectReason::kNotAvailable;
   per_buyer_currency_ = absl::nullopt;
   is_ad_excluded_.Reset();
   is_component_ad_excluded_.Reset();
@@ -247,6 +248,7 @@
       errors_out.push_back(
           base::StringPrintf("%sbidCurrency of '%s' is not a currency code.",
                              error_prefix.c_str(), bid_currency_str.c_str()));
+      reject_reason_ = mojom::RejectReason::kWrongGenerateBidCurrency;
       return false;
     }
     bid_currency = blink::AdCurrency::From(bid_currency_str);
@@ -257,6 +259,7 @@
         "%sbidCurrency mismatch; returned '%s', expected '%s'.",
         error_prefix.c_str(), blink::PrintableAdCurrency(bid_currency).c_str(),
         blink::PrintableAdCurrency(per_buyer_currency_).c_str()));
+    reject_reason_ = mojom::RejectReason::kWrongGenerateBidCurrency;
     return false;
   }
 
diff --git a/content/services/auction_worklet/set_bid_bindings.h b/content/services/auction_worklet/set_bid_bindings.h
index 7862bac4..9ab3928 100644
--- a/content/services/auction_worklet/set_bid_bindings.h
+++ b/content/services/auction_worklet/set_bid_bindings.h
@@ -12,6 +12,7 @@
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/context_recycler.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
+#include "content/services/auction_worklet/public/mojom/reject_reason.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/interest_group/ad_auction_currencies.h"
 #include "third_party/blink/public/common/interest_group/interest_group.h"
@@ -46,6 +47,8 @@
   bool has_bid() const { return !bid_.is_null(); }
   mojom::BidderWorkletBidPtr TakeBid();
 
+  mojom::RejectReason reject_reason() const { return reject_reason_; }
+
   // Returns true if there was no error, and false on error. Note that a valid
   // value that results in no bid is not considered an error.
   bool SetBid(v8::Local<v8::Value> generate_bid_result,
@@ -59,6 +62,7 @@
 
   base::TimeTicks start_;
   bool has_top_level_seller_origin_ = false;
+  mojom::RejectReason reject_reason_ = mojom::RejectReason::kNotAvailable;
 
   raw_ptr<const mojom::BidderWorkletNonSharedParams>
       bidder_worklet_non_shared_params_ = nullptr;
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 2dafc4c..7664535 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1524,6 +1524,8 @@
     "../browser/web_contents/web_contents_view_aura_browsertest.cc",
     "../browser/web_package/signed_exchange_request_handler_browsertest.cc",
     "../browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc",
+    "../browser/webid/test/mock_identity_request_dialog_controller.cc",
+    "../browser/webid/test/mock_identity_request_dialog_controller.h",
     "../browser/webid/test/mock_mdoc_provider.cc",
     "../browser/webid/test/mock_mdoc_provider.h",
     "../browser/webid/test/mock_modal_dialog_view_delegate.cc",
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 350f7cd..da5d465 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -5122,6 +5122,7 @@
 data/attribution_reporting/interop/event_source.json
 data/attribution_reporting/interop/event_source_trigger_priority.json
 data/attribution_reporting/interop/expired_source.json
+data/attribution_reporting/interop/max_aggregatable_reports_per_source.json
 data/attribution_reporting/interop/most_recent_source.json
 data/attribution_reporting/interop/multiple_destinations.json
 data/attribution_reporting/interop/rate_limit_max_attribution_reporting_endpoints.json
diff --git a/content/test/data/accessibility/form-controls/img-form-expected-blink.txt b/content/test/data/accessibility/form-controls/img-form-expected-blink.txt
index 464935f..a2b841f 100644
--- a/content/test/data/accessibility/form-controls/img-form-expected-blink.txt
+++ b/content/test/data/accessibility/form-controls/img-form-expected-blink.txt
@@ -2,10 +2,12 @@
 ++staticText name='Welcome to my example page!'
 ++staticText name='Lengthy instructions on filling out the form.'
 ++staticText name='Imagine hundreds of nodes that are simple content...'
-++staticText ignored name='Enter your name: '
+++labelText ignored
+++++staticText ignored name='Enter your name: '
 ++textField name='Enter your name:'
 ++++genericContainer
-++staticText ignored name='Enter your email: '
+++labelText ignored
+++++staticText ignored name='Enter your email: '
 ++textField name='Enter your email:'
 ++++genericContainer
 ++button name='Subscribe!'
diff --git a/content/test/data/accessibility/html/fencedframe-scrollable-mparch-expected-blink.txt b/content/test/data/accessibility/html/fencedframe-scrollable-mparch-expected-blink.txt
index f651bc4..94edecf 100644
--- a/content/test/data/accessibility/html/fencedframe-scrollable-mparch-expected-blink.txt
+++ b/content/test/data/accessibility/html/fencedframe-scrollable-mparch-expected-blink.txt
@@ -1,4 +1,4 @@
-rootWebArea scrollable=true
+rootWebArea name='done' scrollable=true
 ++genericContainer ignored
 ++++genericContainer
 ++++++iframe name='Scrollable iframe'
diff --git a/content/test/data/accessibility/html/fencedframe-scrollable-mparch.html b/content/test/data/accessibility/html/fencedframe-scrollable-mparch.html
index 5022040a..cdef786 100644
--- a/content/test/data/accessibility/html/fencedframe-scrollable-mparch.html
+++ b/content/test/data/accessibility/html/fencedframe-scrollable-mparch.html
@@ -1,5 +1,6 @@
 <!--
 @BLINK-ALLOW:scrollable=*
+@WAIT-FOR:done
 -->
 <!DOCTYPE html>
 <html style="width:100px; height:100px;">
@@ -7,8 +8,14 @@
   <fencedframe style="width:200px; height: 200px;" aria-label="Scrollable iframe">
   </fencedframe>
   <script>
-    const url = new URL("frame/visible_text.html", location.href);
-    document.querySelector("fencedframe").config = new FencedFrameConfig(url);
+    window.onload = () => {
+      const url = new URL("frame/visible_text.html", location.href);
+      document.querySelector("fencedframe").config = new FencedFrameConfig(url);
+      // Using @WAIT-FOR to check a value inside the fenced frame after it loads
+      // causes the test to flake. Instead, we wait for 500ms to give the fenced
+      // frame enough time to navigate.
+      setTimeout(() => {document.title = "done";}, 500);
+    }
   </script>
 </body>
 </html>
diff --git a/content/test/data/attribution_reporting/interop/max_aggregatable_reports_per_source.json b/content/test/data/attribution_reporting/interop/max_aggregatable_reports_per_source.json
new file mode 100644
index 0000000..6b2c13a
--- /dev/null
+++ b/content/test/data/attribution_reporting/interop/max_aggregatable_reports_per_source.json
@@ -0,0 +1,794 @@
+{
+  "description": "1 navigation source and 21 trigger produce 1 report",
+  "input": {
+    "sources": [
+      {
+        "timestamp": "1643235573000",
+        "registration_request": {
+          "source_origin": "https://source.test",
+          "attribution_src_url": "https://reporter.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "destination": "https://destination.test",
+              "source_event_id": "123",
+              "aggregation_keys": {
+                "a": "0x159"
+              }
+            }
+          }
+        }]
+      }
+    ],
+    "triggers": [
+      {
+        "timestamp": "1643235574000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 1
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574001",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 2
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574002",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 3
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574003",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 4
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574004",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 5
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574005",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 6
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574006",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 7
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574007",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 8
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574008",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 9
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574009",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 10
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574010",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 11
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574011",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 12
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574012",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 13
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574013",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 14
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574014",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 15
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574015",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 16
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574016",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 17
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574017",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 18
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574018",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 19
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574019",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 20
+              }
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235574020",
+        "registration_request": {
+          "attribution_src_url": "https://reporter.test/register-trigger",
+          "destination_origin": "https://destination.test"
+        },
+        "responses": [{
+          "url": "https://reporter.test/register-trigger",
+          "debug_permission": true,
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "debug_reporting": true,
+              "aggregatable_trigger_data": [
+                {
+                  "source_keys": ["a"],
+                  "key_piece": "0x400"
+                }
+              ],
+              "aggregatable_values": {
+                "a": 21
+              }
+            }
+          }
+        }]
+      }
+    ]
+  },
+  "output": {
+    "event_level_results": [],
+    "aggregatable_results": [
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 1
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174000"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 2
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174001"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 3
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174002"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 4
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174003"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 5
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174004"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 6
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174005"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 7
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174006"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 8
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174007"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 9
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174008"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 10
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174009"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 11
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174010"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 12
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174011"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 13
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174012"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 14
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174013"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 15
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174014"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 16
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174015"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 17
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174016"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 18
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174017"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 19
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174018"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination.test",
+          "histograms": [
+            {
+              "key": "0x559",
+              "value": 20
+            }
+          ]
+        },
+        "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+        "report_time": "1643239174019"
+      }
+    ],
+    "verbose_debug_reports": [
+      {
+        "payload": [ {
+          "body": {
+	     "attribution_destination": "https://destination.test",
+             "limit": "20",
+             "source_event_id": "123",
+             "source_site": "https://source.test"
+          },
+          "type": "trigger-aggregate-excessive-reports"
+       } ],
+       "report_time": "1643235574020",
+       "report_url": "https://reporter.test/.well-known/attribution-reporting/debug/verbose"
+      }
+    ]
+  }
+}
diff --git a/content/test/gpu/gpu_tests/__init__.py b/content/test/gpu/gpu_tests/__init__.py
index 9d89cb98..d81a4a8c 100644
--- a/content/test/gpu/gpu_tests/__init__.py
+++ b/content/test/gpu/gpu_tests/__init__.py
@@ -2,4 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import gpu_path_util
 from gpu_path_util import setup_telemetry_paths  # pylint: disable=unused-import
+
+gpu_path_util.AddDirToPathIfNeeded(gpu_path_util.CHROMIUM_SRC_DIR, 'build')
diff --git a/content/test/gpu/gpu_tests/skia_gold/__init__.py b/content/test/gpu/gpu_tests/skia_gold/__init__.py
deleted file mode 100644
index 8b0d81aa..0000000
--- a/content/test/gpu/gpu_tests/skia_gold/__init__.py
+++ /dev/null
@@ -1,7 +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.
-
-import gpu_path_util
-
-gpu_path_util.AddDirToPathIfNeeded(gpu_path_util.CHROMIUM_SRC_DIR, 'build')
diff --git a/content/test/gpu/gpu_tests/skia_gold/gpu_skia_gold_session_manager.py b/content/test/gpu/gpu_tests/skia_gold/gpu_skia_gold_session_manager.py
deleted file mode 100644
index 080402d..0000000
--- a/content/test/gpu/gpu_tests/skia_gold/gpu_skia_gold_session_manager.py
+++ /dev/null
@@ -1,13 +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.
-"""GPU impl of //testing/skia_gold_common/skia_gold_session_manager.py."""
-
-from skia_gold_common import output_managerless_skia_gold_session
-from skia_gold_common import skia_gold_session_manager as sgsm
-
-
-class GpuSkiaGoldSessionManager(sgsm.SkiaGoldSessionManager):
-  @staticmethod
-  def GetSessionClass():
-    return output_managerless_skia_gold_session.OutputManagerlessSkiaGoldSession
diff --git a/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py b/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
index 2567ec47..8d26f07 100644
--- a/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
+++ b/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
@@ -18,10 +18,10 @@
 from gpu_tests import gpu_helper
 from gpu_tests import gpu_integration_test
 from gpu_tests import pixel_test_pages
-from gpu_tests.skia_gold import gpu_skia_gold_session_manager as sgsm
 
 from skia_gold_common import skia_gold_properties as sgp
 from skia_gold_common import skia_gold_session as sgs
+from skia_gold_common import skia_gold_session_manager as sgsm
 
 import gpu_path_util
 
@@ -83,9 +83,9 @@
     return cls._skia_gold_properties
 
   @classmethod
-  def GetSkiaGoldSessionManager(cls) -> sgsm.GpuSkiaGoldSessionManager:
+  def GetSkiaGoldSessionManager(cls) -> sgsm.SkiaGoldSessionManager:
     if not cls._skia_gold_session_manager:
-      cls._skia_gold_session_manager = sgsm.GpuSkiaGoldSessionManager(
+      cls._skia_gold_session_manager = sgsm.SkiaGoldSessionManager(
           cls._skia_gold_temp_dir, cls.GetSkiaGoldProperties())
     return cls._skia_gold_session_manager
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt
index 86f9434..207aed4 100644
--- a/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
index 0ed4b3e..6a67eeb 100644
--- a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index 872b3b10..56a27561 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
index f44168a..a3fef8c0 100644
--- a/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
index b4aae8e0..003b791 100644
--- a/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
index 39566c0..55b716d 100644
--- a/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt
index fdf7ed0..f137f266 100644
--- a/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 0e087b6..b9764cb 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt
index f44168a..a3fef8c0 100644
--- a/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt
index 9b91f3e..7a76098 100644
--- a/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
index 6acecac..849d20b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
index eaf9c84..2bdfd34 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
@@ -126,9 +128,10 @@
 crbug.com/1377842 [ android ] WebCodecs_EncodeColorSpace_avc1.42001E_prefer-software [ Failure ]
 crbug.com/1377842 [ android ] WebCodecs_EncodeColorSpace_vp09.00.10.08_prefer-hardware [ Failure ]
 
-
 crbug.com/1429736 [ chromeos chromeos-board-kevin ] WebCodecs_EncodeColorSpace_avc1.42001E_prefer-hardware [ Failure ]
 
+crbug.com/1444151 [ mac apple-apple-m2 ] WebCodecs_EncodingRateControl_avc1.420034_prefer-hardware_constant_* [ Failure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 1cbb0a8..67fdba3 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
@@ -390,17 +392,17 @@
 crbug.com/1316389 [ angle-metal intel passthrough ventura ] deqp/functional/gles3/transformfeedback/random_separate_points.html [ Failure ]
 crbug.com/angleproject/8026 [ angle-metal intel monterey passthrough ] conformance2/uniforms/dependent-buffer-change.html [ Failure ]
 
-## Metal M1 ##
+## Metal Apple Silicon ##
 
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/framebufferblit/rect_03.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/framebufferblit/rect_04.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/shadertexturefunction/texturegrad.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturefiltering/cube_combinations_00.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturefiltering/cube_combinations_02.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturefiltering/cube_combinations_04.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturefiltering/cube_sizes_03.html [ Failure ]
-crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturefiltering/cube_sizes_04.html [ Failure ]
-crbug.com/1298619 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/occlusionquery_strict.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/framebufferblit/rect_03.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/framebufferblit/rect_04.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/shadertexturefunction/texturegrad.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/texturefiltering/cube_combinations_00.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/texturefiltering/cube_combinations_02.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/texturefiltering/cube_combinations_04.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/texturefiltering/cube_sizes_03.html [ Failure ]
+crbug.com/angleproject/6430 [ mac passthrough angle-metal apple ] deqp/functional/gles3/texturefiltering/cube_sizes_04.html [ Failure ]
+crbug.com/1298619 [ mac passthrough angle-metal apple ] deqp/functional/gles3/occlusionquery_strict.html [ Failure ]
 
 ######################################################################
 # Mac failures (mainly OpenGL; some need to be reevaluated on Metal) #
@@ -561,23 +563,23 @@
 
 ## Mac / M1 / OpenGL ##
 
-crbug.com/angleproject/5223 [ angle-opengl apple-apple-m1 mac mac-arm64 no-asan oop-c passthrough ] conformance2/textures/misc/tex-mipmap-levels.html [ Failure ]
-crbug.com/1130112 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_combinations_00.html [ Failure ]
-crbug.com/1130112 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_combinations_02.html [ Failure ]
-crbug.com/1130112 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_combinations_04.html [ Failure ]
-crbug.com/1130112 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_sizes_03.html [ Failure ]
-crbug.com/1130112 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_sizes_04.html [ Failure ]
-crbug.com/1130117 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/multisample/fbo_4_samples.html [ Failure ]
-crbug.com/1130117 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/multisample/fbo_max_samples.html [ Failure ]
-crbug.com/1130118 [ mac apple-apple-m1 passthrough angle-opengl ] conformance2/rendering/blitframebuffer-filter-srgb.html [ Failure ]
-crbug.com/1130118 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/framebufferblit/rect_03.html [ Failure ]
-crbug.com/1130118 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/framebufferblit/rect_04.html [ Failure ]
-crbug.com/1130119 [ mac apple-apple-m1 passthrough angle-opengl ] conformance2/glsl3/vector-dynamic-indexing.html [ Failure ]
-crbug.com/1130119 [ mac apple-apple-m1 passthrough angle-opengl ] conformance2/textures/misc/tex-base-level-bug.html [ Failure ]
-crbug.com/1130119 [ mac apple-apple-m1 passthrough angle-opengl ] conformance2/rendering/framebuffer-completeness-unaffected.html [ Failure ]
-crbug.com/1130119 [ mac apple-apple-m1 passthrough angle-opengl ] conformance2/rendering/framebuffer-render-to-layer.html [ Failure ]
-crbug.com/1130119 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/fbocompleteness.html [ Failure ]
-crbug.com/1130119 [ mac apple-apple-m1 passthrough angle-opengl ] deqp/functional/gles3/shadertexturefunction/texturegrad.html [ Failure ]
+crbug.com/angleproject/5223 [ angle-opengl apple mac mac-arm64 no-asan oop-c passthrough ] conformance2/textures/misc/tex-mipmap-levels.html [ Failure ]
+crbug.com/1130112 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_combinations_00.html [ Failure ]
+crbug.com/1130112 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_combinations_02.html [ Failure ]
+crbug.com/1130112 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_combinations_04.html [ Failure ]
+crbug.com/1130112 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_sizes_03.html [ Failure ]
+crbug.com/1130112 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/texturefiltering/cube_sizes_04.html [ Failure ]
+crbug.com/1130117 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/multisample/fbo_4_samples.html [ Failure ]
+crbug.com/1130117 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/multisample/fbo_max_samples.html [ Failure ]
+crbug.com/1130118 [ mac apple passthrough angle-opengl ] conformance2/rendering/blitframebuffer-filter-srgb.html [ Failure ]
+crbug.com/1130118 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/framebufferblit/rect_03.html [ Failure ]
+crbug.com/1130118 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/framebufferblit/rect_04.html [ Failure ]
+crbug.com/1130119 [ mac apple passthrough angle-opengl ] conformance2/glsl3/vector-dynamic-indexing.html [ Failure ]
+crbug.com/1130119 [ mac apple passthrough angle-opengl ] conformance2/textures/misc/tex-base-level-bug.html [ Failure ]
+crbug.com/1130119 [ mac apple passthrough angle-opengl ] conformance2/rendering/framebuffer-completeness-unaffected.html [ Failure ]
+crbug.com/1130119 [ mac apple passthrough angle-opengl ] conformance2/rendering/framebuffer-render-to-layer.html [ Failure ]
+crbug.com/1130119 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/fbocompleteness.html [ Failure ]
+crbug.com/1130119 [ mac apple passthrough angle-opengl ] deqp/functional/gles3/shadertexturefunction/texturegrad.html [ Failure ]
 crbug.com/1240443 [ angle-opengl apple-apple-m1 monterey passthrough ] conformance/renderbuffers/stencil-renderbuffer-initialization.html [ Failure ]
 
 # Newly added tests on NVIDIA passthrough
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index fb4a8e5..4b31749 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -25,7 +25,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/gpu/validate_tag_consistency.py b/content/test/gpu/validate_tag_consistency.py
index 3049b87..f09e250 100755
--- a/content/test/gpu/validate_tag_consistency.py
+++ b/content/test/gpu/validate_tag_consistency.py
@@ -38,7 +38,9 @@
 #         lacros-chrome cros-chrome ]
 # GPU
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x67ef amd-0x6821 amd-0x7340
-#         apple apple-apple-m1 apple-angle-metal-renderer:-apple-m1
+#         apple apple-apple-m1 apple-apple-m2
+#             apple-angle-metal-renderer:-apple-m1
+#             apple-angle-metal-renderer:-apple-m2
 #         arm
 #         google google-0xffff google-0xc0de
 #         intel intel-gen-9 intel-gen-12 intel-0xa2e intel-0xd26 intel-0xa011
diff --git a/content/test/ppapi/ppapi_browsertest.cc b/content/test/ppapi/ppapi_browsertest.cc
index 5281964e..679399fb 100644
--- a/content/test/ppapi/ppapi_browsertest.cc
+++ b/content/test/ppapi/ppapi_browsertest.cc
@@ -82,7 +82,7 @@
   // In other tests, we use one call to RunTest so that the tests can all run
   // in one plugin instance. This saves time on loading the plugin (especially
   // for NaCl). Here, we actually want to destroy the Instance, to test whether
-  // the destructor can run ExecuteScript successfully. That's why we have two
+  // the destructor can run ExecJs successfully. That's why we have two
   // separate calls to RunTest; the second one forces a navigation which
   // destroys the instance from the prior RunTest.
   // See test_instance_deprecated.cc for more information.
diff --git a/content/utility/services.cc b/content/utility/services.cc
index 87e42dc9..457b153d 100644
--- a/content/utility/services.cc
+++ b/content/utility/services.cc
@@ -105,6 +105,7 @@
 #endif  // BUILDFLAG(ENABLE_ACCESSIBILITY_SERVICE)
 
 #if BUILDFLAG(IS_LINUX)
+#include "media/capture/capture_switches.h"
 #include "services/viz/public/cpp/gpu/gpu.h"
 #endif  // BUILDFLAG(IS_LINUX)
 
@@ -305,12 +306,15 @@
   auto service = std::make_unique<UtilityThreadVideoCaptureServiceImpl>(
       std::move(receiver), base::SingleThreadTaskRunner::GetCurrentDefault());
 #if BUILDFLAG(IS_LINUX)
-  mojo::PendingRemote<viz::mojom::Gpu> remote_gpu;
-  content::UtilityThread::Get()->BindHostReceiver(
-      remote_gpu.InitWithNewPipeAndPassReceiver());
-  std::unique_ptr<viz::Gpu> viz_gpu = viz::Gpu::Create(
-      std::move(remote_gpu), content::UtilityThread::Get()->GetIOTaskRunner());
-  service->SetVizGpu(std::move(viz_gpu));
+  if (switches::IsVideoCaptureUseGpuMemoryBufferEnabled()) {
+    mojo::PendingRemote<viz::mojom::Gpu> remote_gpu;
+    content::UtilityThread::Get()->BindHostReceiver(
+        remote_gpu.InitWithNewPipeAndPassReceiver());
+    std::unique_ptr<viz::Gpu> viz_gpu =
+        viz::Gpu::Create(std::move(remote_gpu),
+                         content::UtilityThread::Get()->GetIOTaskRunner());
+    service->SetVizGpu(std::move(viz_gpu));
+  }
 #endif  // BUILDFLAG(IS_LINUX)
   return service;
 }
diff --git a/crypto/apple_keychain_ios.mm b/crypto/apple_keychain_ios.mm
index a9cac77..f124437 100644
--- a/crypto/apple_keychain_ios.mm
+++ b/crypto/apple_keychain_ios.mm
@@ -6,7 +6,7 @@
 
 #import <Foundation/Foundation.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 
@@ -74,7 +74,7 @@
   // Set the password.
   NSData* password = [NSData dataWithBytes:passwordData length:passwordLength];
   CFDictionarySetValue(keychain_data, kSecValueData,
-                       base::mac::NSToCFPtrCast(password));
+                       base::apple::NSToCFPtrCast(password));
 
   // If this is not a creation, no structural information is needed.
   if (action != kKeychainActionCreate) {
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 2cd70af7..b1f5e18 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -27,7 +27,9 @@
       "fido/mac/authenticator_unittest.mm",
       "fido/mac/browsing_data_deletion_unittest.mm",
       "fido/mac/credential_store_unittest.mm",
+      "fido/mac/fake_icloud_keychain_sys.mm",
       "fido/mac/get_assertion_operation_unittest_mac.mm",
+      "fido/mac/icloud_keychain_unittest.mm",
       "fido/mac/make_credential_operation_unittest_mac.mm",
     ]
     configs += [ "//build/config/compiler:enable_arc" ]
@@ -380,6 +382,7 @@
     frameworks += [
       "IOBluetooth.framework",
       "Security.framework",
+      "AuthenticationServices.framework",
     ]
   }
 
diff --git a/device/bluetooth/bluetooth_adapter_factory.h b/device/bluetooth/bluetooth_adapter_factory.h
index a3ee043..777d5815 100644
--- a/device/bluetooth/bluetooth_adapter_factory.h
+++ b/device/bluetooth/bluetooth_adapter_factory.h
@@ -6,6 +6,7 @@
 #define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_FACTORY_H_
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.cc b/device/bluetooth/floss/bluetooth_adapter_floss.cc
index c20d5815..d305ce0 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.cc
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.cc
@@ -743,6 +743,15 @@
 
   BLUETOOTH_LOG(EVENT) << __func__ << device_found;
 
+  UpdateDeviceProperties(true, device_found);
+}
+
+void BluetoothAdapterFloss::UpdateDeviceProperties(
+    bool is_triggered_by_inquiry,
+    const FlossDeviceId& device_found) {
+  DCHECK(FlossDBusManager::Get());
+  DCHECK(IsPresent());
+
   auto device_floss = CreateBluetoothDeviceFloss(device_found);
   BluetoothDeviceFloss* new_device_ptr = nullptr;
 
@@ -759,9 +768,16 @@
         static_cast<BluetoothDeviceFloss*>(devices_[canonical_address].get());
   }
 
+  BluetoothDeviceFloss::PropertiesState state =
+      BluetoothDeviceFloss::PropertiesState::kTriggeredByScan;
+  if (is_triggered_by_inquiry) {
+    state = BluetoothDeviceFloss::PropertiesState::kTriggeredByInquiry;
+  }
+
   // Trigger property reads for new devices.
   if (new_device_ptr) {
     new_device_ptr->InitializeDeviceProperties(
+        state,
         base::BindOnce(&BluetoothAdapterFloss::OnInitializeDeviceProperties,
                        weak_ptr_factory_.GetWeakPtr(), new_device_ptr));
 
@@ -782,12 +798,15 @@
       static_cast<BluetoothDeviceFloss*>(devices_[canonical_address].get());
 
   // If the name has changed, we should also reinitialize the device properties.
+  // For dual mode devices, if this is the first inquiry result received or
+  // first scan result received, reinitialize the device properties.
   // NotifyDeviceChanged will get called after properties are re-init.
-  if (device_floss->GetName() && device->GetName() != device_floss->GetName()) {
-    device->SetName(device_floss->GetName().value_or(""));
+  if ((!device_found.name.empty() && device->GetName() != device_found.name) ||
+      !(device->GetPropertiesState() & state)) {
+    device->SetName(device_found.name);
     device->InitializeDeviceProperties(
-        base::BindOnce(&BluetoothAdapterFloss::NotifyDeviceChanged,
-                       weak_ptr_factory_.GetWeakPtr(), device));
+        state, base::BindOnce(&BluetoothAdapterFloss::NotifyDeviceChanged,
+                              weak_ptr_factory_.GetWeakPtr(), device));
   }
 }
 
@@ -958,7 +977,7 @@
   if (!device) {
     BLUETOOTH_LOG(EVENT) << "Adding newly connected device to devices_ map: "
                          << device_id.address;
-    AdapterFoundDevice(device_id);
+    UpdateDeviceProperties(false, device_id);
     return;
   }
 
@@ -1383,10 +1402,10 @@
     observer.DeviceAdvertisementReceived(this, device_ptr, scan_result.rssi,
                                          scan_result.adv_data);
 
-  // We are currently in an LE discovery session. Also emit a DeviceFound event
-  // since we have updated data for this device.
+  // We are currently in an LE discovery session. Update properties and emit
+  // a |DeviceFound| if newly found or |DeviceChanged|.
   if (le_discovery_session_) {
-    AdapterFoundDevice(device_ptr->AsFlossDeviceId());
+    UpdateDeviceProperties(false, device_ptr->AsFlossDeviceId());
   }
 }
 
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.h b/device/bluetooth/floss/bluetooth_adapter_floss.h
index bf126baa..b1f8e2f 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.h
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.h
@@ -214,6 +214,10 @@
   std::unique_ptr<BluetoothDeviceFloss> CreateBluetoothDeviceFloss(
       FlossDeviceId device);
 
+  // Helper function to update device properties if necessary
+  void UpdateDeviceProperties(bool is_triggered_by_inquiry,
+                              const FlossDeviceId& device_found);
+
   // Handle responses to most method calls
   void OnMethodResponse(base::OnceClosure callback,
                         ErrorCallback error_callback,
diff --git a/device/bluetooth/floss/bluetooth_device_floss.cc b/device/bluetooth/floss/bluetooth_device_floss.cc
index 82c1b6b..21ff473 100644
--- a/device/bluetooth/floss/bluetooth_device_floss.cc
+++ b/device/bluetooth/floss/bluetooth_device_floss.cc
@@ -828,13 +828,14 @@
 }
 
 void BluetoothDeviceFloss::InitializeDeviceProperties(
+    PropertiesState state,
     base::OnceClosure callback) {
   // If a property read is already active, don't re-run it.
-  if (property_reads_triggered_) {
+  if (IsReadingProperties()) {
     return;
   }
 
-  property_reads_triggered_ = true;
+  property_reads_triggered_ = state;
   pending_callback_on_init_props_ = std::move(callback);
   // This must be incremented when adding more properties below
   // and followed up with a TriggerInitDevicePropertiesCallback()
@@ -861,8 +862,9 @@
 
 void BluetoothDeviceFloss::TriggerInitDevicePropertiesCallback() {
   if (--num_pending_properties_ == 0 && pending_callback_on_init_props_) {
-    property_reads_completed_ = true;
-    property_reads_triggered_ = false;
+    property_reads_completed_ = static_cast<PropertiesState>(
+        property_reads_completed_ | property_reads_triggered_);
+    property_reads_triggered_ = PropertiesState::kNotRead;
     std::move(*pending_callback_on_init_props_).Run();
     pending_callback_on_init_props_ = absl::nullopt;
   }
diff --git a/device/bluetooth/floss/bluetooth_device_floss.h b/device/bluetooth/floss/bluetooth_device_floss.h
index e428cc1..b77db31a 100644
--- a/device/bluetooth/floss/bluetooth_device_floss.h
+++ b/device/bluetooth/floss/bluetooth_device_floss.h
@@ -43,6 +43,12 @@
     kGattConnecting,
     kGattConnected,
   };
+  enum PropertiesState : uint32_t {
+    kNotRead = 0,
+    kTriggeredByScan = 1 << 1,
+    kTriggeredByInquiry = 1 << 2,
+    kTriggeredbyBoth = (kTriggeredByScan | kTriggeredByInquiry)
+  };
 
   BluetoothDeviceFloss(const BluetoothDeviceFloss&) = delete;
   BluetoothDeviceFloss& operator=(const BluetoothDeviceFloss&) = delete;
@@ -137,9 +143,17 @@
 
   BluetoothPairingFloss* pairing() const { return pairing_.get(); }
 
-  void InitializeDeviceProperties(base::OnceClosure callback);
-  bool IsReadingProperties() const { return property_reads_triggered_; }
-  bool HasReadProperties() const { return property_reads_completed_; }
+  void InitializeDeviceProperties(PropertiesState state,
+                                  base::OnceClosure callback);
+  bool IsReadingProperties() const {
+    return property_reads_triggered_ != PropertiesState::kNotRead;
+  }
+  bool HasReadProperties() const {
+    return property_reads_completed_ != PropertiesState::kNotRead;
+  }
+  PropertiesState GetPropertiesState() const {
+    return property_reads_completed_;
+  }
 
   // FlossGattClientObserver overrides
   void GattClientConnectionState(GattStatus status,
@@ -274,10 +288,10 @@
   bool svc_resolved_ = false;
 
   // Have we triggered initial property reads?
-  bool property_reads_triggered_ = false;
+  PropertiesState property_reads_triggered_ = PropertiesState::kNotRead;
 
   // Have we completed reading properties?
-  bool property_reads_completed_ = false;
+  PropertiesState property_reads_completed_ = PropertiesState::kNotRead;
 
   // Specific uuid to search for after gatt connection is established. If this
   // is not set, then we do full discovery.
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn
index 905426a..1c826e8 100644
--- a/device/fido/BUILD.gn
+++ b/device/fido/BUILD.gn
@@ -177,6 +177,8 @@
       "hid/fido_hid_packet.h",
       "large_blob.cc",
       "large_blob.h",
+      "mac/icloud_keychain.h",
+      "mac/icloud_keychain_sys.h",
       "make_credential_request_handler.cc",
       "make_credential_request_handler.h",
       "make_credential_task.cc",
@@ -228,6 +230,8 @@
       "mac/discovery.h",
       "mac/get_assertion_operation.h",
       "mac/get_assertion_operation.mm",
+      "mac/icloud_keychain.mm",
+      "mac/icloud_keychain_sys.mm",
       "mac/keychain.h",
       "mac/keychain.mm",
       "mac/make_credential_operation.h",
@@ -243,6 +247,7 @@
       "Foundation.framework",
       "LocalAuthentication.framework",
       "Security.framework",
+      "AuthenticationServices.framework",
     ]
 
     configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/device/fido/features.cc b/device/fido/features.cc
index 8a79497..8fec6f7 100644
--- a/device/fido/features.cc
+++ b/device/fido/features.cc
@@ -70,4 +70,9 @@
              "WebAuthenticationPinRequiredMeansNotRecognized",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Added in M115. Remove in or after M118
+BASE_FEATURE(kWebAuthnHybridLinkWithoutNotifications,
+             "WebAuthenticationHybridLinkWithoutNotifications",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace device
diff --git a/device/fido/features.h b/device/fido/features.h
index 0f94ac2..e859496 100644
--- a/device/fido/features.h
+++ b/device/fido/features.h
@@ -67,6 +67,11 @@
 COMPONENT_EXPORT(DEVICE_FIDO)
 BASE_DECLARE_FEATURE(kWebAuthnPinRequiredMeansNotRecognized);
 
+// Advertise hybrid prelinking on Android even if the app doesn't have
+// notifications permission.
+COMPONENT_EXPORT(DEVICE_FIDO)
+BASE_DECLARE_FEATURE(kWebAuthnHybridLinkWithoutNotifications);
+
 }  // namespace device
 
 #endif  // DEVICE_FIDO_FEATURES_H_
diff --git a/device/fido/fido_types.h b/device/fido/fido_types.h
index bd3500b..9dd10941 100644
--- a/device/fido/fido_types.h
+++ b/device/fido/fido_types.h
@@ -93,10 +93,11 @@
 // AuthenticatorType enumerates the different types of authenticators that this
 // code handles.
 enum class AuthenticatorType {
-  kWinNative,  // i.e. webauthn.dll
-  kTouchID,    // the Chrome-native Touch ID integration on macOS
-  kChromeOS,   // the platform authenticator on Chrome OS
-  kPhone,      // the credential can be exercised via hybrid CTAP
+  kWinNative,       // i.e. webauthn.dll
+  kTouchID,         // the Chrome-native Touch ID integration on macOS
+  kChromeOS,        // the platform authenticator on Chrome OS
+  kPhone,           // the credential can be exercised via hybrid CTAP
+  kICloudKeychain,  // iCloud Keychain on macOS
   kOther,
 };
 
diff --git a/device/fido/mac/credential_store.mm b/device/fido/mac/credential_store.mm
index 5cc0aa1..fd06035 100644
--- a/device/fido/mac/credential_store.mm
+++ b/device/fido/mac/credential_store.mm
@@ -9,11 +9,11 @@
 #import <LocalAuthentication/LocalAuthentication.h>
 #import <Security/Security.h>
 
+#include "base/apple/bridging.h"
 #include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/logging.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
 #include "base/mac/scoped_cftyperef.h"
@@ -230,7 +230,7 @@
   CFDictionarySetValue(params, kSecAttrKeyType,
                        kSecAttrKeyTypeECSECPrimeRandom);
   CFDictionarySetValue(params, kSecAttrKeySizeInBits,
-                       base::mac::NSToCFPtrCast(@256));
+                       base::apple::NSToCFPtrCast(@256));
   CFDictionarySetValue(params, kSecAttrSynchronizable, kCFBooleanFalse);
   CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
 
@@ -242,15 +242,15 @@
           user, discoverable == kDiscoverable);
   const std::vector<uint8_t> sealed_metadata = SealCredentialMetadata(
       config_.metadata_secret, rp_id, credential_metadata);
-  CFDictionarySetValue(
-      params, kSecAttrApplicationTag,
-      base::mac::NSToCFPtrCast([NSData dataWithBytes:sealed_metadata.data()
-                                              length:sealed_metadata.size()]));
+  CFDictionarySetValue(params, kSecAttrApplicationTag,
+                       base::apple::NSToCFPtrCast([NSData
+                           dataWithBytes:sealed_metadata.data()
+                                  length:sealed_metadata.size()]));
   const std::vector<uint8_t> credential_id = GenerateRandomCredentialId();
   CFDictionarySetValue(
       params, kSecAttrApplicationLabel,
-      base::mac::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                              length:credential_id.size()]));
+      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
+                                                length:credential_id.size()]));
   base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
       CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                 &kCFTypeDictionaryKeyCallBacks,
@@ -323,7 +323,7 @@
   CFDictionarySetValue(params, kSecAttrKeyType,
                        kSecAttrKeyTypeECSECPrimeRandom);
   CFDictionarySetValue(params, kSecAttrKeySizeInBits,
-                       base::mac::NSToCFPtrCast(@256));
+                       base::apple::NSToCFPtrCast(@256));
   CFDictionarySetValue(params, kSecAttrSynchronizable, kCFBooleanFalse);
   CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
 
@@ -335,8 +335,8 @@
                            config_.metadata_secret, rp_id, user.id)));
   CFDictionarySetValue(
       params, kSecAttrApplicationLabel,
-      base::mac::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                              length:credential_id.size()]));
+      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
+                                                length:credential_id.size()]));
   base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
       CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                 &kCFTypeDictionaryKeyCallBacks,
@@ -654,8 +654,8 @@
   CFDictionarySetValue(query, kSecClass, kSecClassKey);
   CFDictionarySetValue(
       query, kSecAttrApplicationLabel,
-      base::mac::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                              length:credential_id.size()]));
+      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
+                                                length:credential_id.size()]));
   OSStatus status = Keychain::GetInstance().ItemDelete(query);
   if (status != errSecSuccess) {
     OSSTATUS_DLOG(ERROR, status) << "SecItemDelete failed";
@@ -696,7 +696,7 @@
       std::vector<uint8_t> sealed_metadata = SealCredentialMetadata(
           config_.metadata_secret, credential.rp_id, credential.metadata);
       CFDictionarySetValue(params, kSecAttrApplicationTag,
-                           base::mac::NSToCFPtrCast([NSData
+                           base::apple::NSToCFPtrCast([NSData
                                dataWithBytes:sealed_metadata.data()
                                       length:sealed_metadata.size()]));
       found_credential = true;
@@ -718,8 +718,8 @@
   CFDictionarySetValue(query, kSecClass, kSecClassKey);
   CFDictionarySetValue(
       query, kSecAttrApplicationLabel,
-      base::mac::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                              length:credential_id.size()]));
+      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
+                                                length:credential_id.size()]));
   OSStatus status = Keychain::GetInstance().ItemUpdate(query, params);
   if (status != errSecSuccess) {
     OSSTATUS_DLOG(ERROR, status) << "SecItemUpdate failed";
diff --git a/device/fido/mac/fake_icloud_keychain_sys.h b/device/fido/mac/fake_icloud_keychain_sys.h
new file mode 100644
index 0000000..e4ff71d
--- /dev/null
+++ b/device/fido/mac/fake_icloud_keychain_sys.h
@@ -0,0 +1,111 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_FIDO_MAC_FAKE_ICLOUD_KEYCHAIN_SYS_H_
+#define DEVICE_FIDO_MAC_FAKE_ICLOUD_KEYCHAIN_SYS_H_
+
+#if !defined(__OBJC__)
+#error "This header is only for Objective C++ compilation units"
+#endif
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "device/fido/discoverable_credential_metadata.h"
+#include "device/fido/mac/icloud_keychain_sys.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if !defined(__OBJC__) || !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@class NSWindow;
+
+namespace device::fido::icloud_keychain {
+
+// FakeSystemInterface allows the macOS passkey API to be simulated for tests.
+// One can create an instance with `base::MakeRefCounted<FakeSystemInterface>()`
+// and install it with `SetSystemInterfaceForTesting`.
+class API_AVAILABLE(macos(13.3)) FakeSystemInterface : public SystemInterface {
+ public:
+  FakeSystemInterface();
+
+  // set_auth_state sets the state that `GetAuthState` will report.
+  void set_auth_state(AuthState auth_state);
+
+  // set_next_auth_state sets the state the state that will become current after
+  // a call to `AuthorizeAndContinue`. This must be called before
+  // `AuthorizeAndContinue`.
+  void set_next_auth_state(AuthState next_auth_state);
+
+  // SetMakeCredentialResult sets the values that will be returned from the next
+  // call to `MakeCredential`. If not set, `MakeCredential` will return an
+  // error.
+  void SetMakeCredentialResult(base::span<const uint8_t> attestation_obj_bytes,
+                               base::span<const uint8_t> credential_id);
+
+  // SetMakeCredentialError sets the code of the `NSError` that will be returned
+  // from the next `MakeCredential` call.
+  void SetMakeCredentialError(int code);
+
+  // SetGetAssertionResult sets the values that will be returned from the next
+  // call to `GetAssertion`. If not set, `GetAssertion` will return an error.
+  void SetGetAssertionResult(base::span<const uint8_t> authenticator_data,
+                             base::span<const uint8_t> signature,
+                             base::span<const uint8_t> user_id,
+                             base::span<const uint8_t> credential_id);
+
+  // SetCredentials causes `GetPlatformCredentials` to simulate that the given
+  // credentials are on the system. (Note that `GetPlatformCredentials` ignores
+  // the requested RP ID so all credentials specified here will be returned.)
+  void SetCredentials(std::vector<DiscoverableCredentialMetadata> creds);
+
+  // SystemInterface:
+  bool IsAvailable() const override;
+
+  AuthState GetAuthState() override;
+
+  void AuthorizeAndContinue(base::OnceCallback<void()> callback) override;
+
+  void GetPlatformCredentials(
+      const std::string& rp_id,
+      void (^)(NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*))
+      override;
+
+  void MakeCredential(
+      NSWindow* window,
+      CtapMakeCredentialRequest request,
+      base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+          callback) override;
+
+  void GetAssertion(
+      NSWindow* window,
+      CtapGetAssertionRequest request,
+      base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+          callback) override;
+
+ protected:
+  friend class base::RefCounted<SystemInterface>;
+  friend class base::RefCounted<FakeSystemInterface>;
+  ~FakeSystemInterface() override;
+
+  AuthState auth_state_ = kAuthAuthorized;
+  absl::optional<AuthState> next_auth_state_;
+
+  absl::optional<int> make_credential_error_code_;
+  absl::optional<std::vector<uint8_t>>
+      make_credential_attestation_object_bytes_;
+  absl::optional<std::vector<uint8_t>> make_credential_credential_id_;
+
+  absl::optional<std::vector<uint8_t>> get_assertion_authenticator_data_;
+  absl::optional<std::vector<uint8_t>> get_assertion_signature_;
+  absl::optional<std::vector<uint8_t>> get_assertion_user_id_;
+  absl::optional<std::vector<uint8_t>> get_assertion_credential_id_;
+
+  std::vector<DiscoverableCredentialMetadata> creds_;
+};
+
+}  // namespace device::fido::icloud_keychain
+
+#endif  // DEVICE_FIDO_MAC_FAKE_ICLOUD_KEYCHAIN_SYS_H_
diff --git a/device/fido/mac/fake_icloud_keychain_sys.mm b/device/fido/mac/fake_icloud_keychain_sys.mm
new file mode 100644
index 0000000..eebc5ea87
--- /dev/null
+++ b/device/fido/mac/fake_icloud_keychain_sys.mm
@@ -0,0 +1,270 @@
+// 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 <vector>
+
+#import <AuthenticationServices/AuthenticationServices.h>
+#import <objc/message.h>
+
+#include "base/functional/callback.h"
+#include "base/strings/sys_string_conversions.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "device/fido/mac/fake_icloud_keychain_sys.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// A number of AuthenticationServices objects are subclassed so that the
+// values of readonly properties can be overridden in tests.
+
+API_AVAILABLE(macos(13.3))
+@interface FakeBrowserPlatformPublicKeyCredential
+    : ASAuthorizationWebBrowserPlatformPublicKeyCredential
+@property(nonatomic, copy) NSData* credentialID;
+@property(nonatomic, copy) NSString* name;
+@property(nonatomic, copy) NSString* relyingParty;
+@property(nonatomic, copy) NSData* userHandle;
+@end
+
+@implementation FakeBrowserPlatformPublicKeyCredential
+@synthesize credentialID = _credentialID;
+@synthesize name = _name;
+@synthesize relyingParty = _relyingParty;
+@synthesize userHandle = _userHandle;
+@end
+
+API_AVAILABLE(macos(13.3))
+@interface FakeASAuthorization : ASAuthorization
+@property(nonatomic, strong) id<ASAuthorizationCredential> credential;
+@end
+
+@implementation FakeASAuthorization
+@synthesize credential = _credential;
+@end
+
+API_AVAILABLE(macos(13.3))
+@interface FakeASAuthorizationPublicKeyCredentialRegistration
+    : NSObject <ASAuthorizationPublicKeyCredentialRegistration>
+@property(nonatomic, copy) NSData* rawAttestationObject;
+@property(nonatomic, copy) NSData* rawClientDataJSON;
+@property(nonatomic, copy) NSData* credentialID;
+@end
+
+@implementation FakeASAuthorizationPublicKeyCredentialRegistration
+@synthesize rawAttestationObject = _rawAttestationObject;
+@synthesize rawClientDataJSON = _rawClientDataJSON;
+@synthesize credentialID = _credentialID;
+
++ (BOOL)supportsSecureCoding {
+  return NO;
+}
+
+- (instancetype)initWithCoder:(NSCoder*)aDecoder {
+  NOTREACHED();
+  return self;
+}
+
+- (void)encodeWithCoder:(NSCoder*)aCoder {
+  NOTREACHED();
+}
+
+- (id)copyWithZone:(NSZone*)zone {
+  NOTREACHED();
+  return self;
+}
+@end
+
+API_AVAILABLE(macos(13.3))
+@interface FakeASAuthorizationPublicKeyCredentialAssertion
+    : NSObject <ASAuthorizationPublicKeyCredentialAssertion>
+@property(nonatomic, copy) NSData* rawAuthenticatorData;
+@property(nonatomic, copy) NSData* signature;
+@property(nonatomic, copy) NSData* userID;
+@property(nonatomic, copy) NSData* credentialID;
+@property(nonatomic, copy) NSData* rawClientDataJSON;
+@end
+
+@implementation FakeASAuthorizationPublicKeyCredentialAssertion
+@synthesize rawAuthenticatorData = _rawAuthenticatorData;
+@synthesize signature = _signature;
+@synthesize userID = _userID;
+@synthesize credentialID = _credentialID;
+@synthesize rawClientDataJSON = _rawClientDataJSON;
+
++ (BOOL)supportsSecureCoding {
+  return NO;
+}
+
+- (instancetype)initWithCoder:(NSCoder*)aDecoder {
+  NOTREACHED();
+  return self;
+}
+
+- (void)encodeWithCoder:(NSCoder*)aCoder {
+  NOTREACHED();
+}
+
+- (id)copyWithZone:(NSZone*)zone {
+  NOTREACHED();
+  return self;
+}
+@end
+
+namespace device::fido::icloud_keychain {
+
+namespace {
+NSData* ToNSData(base::span<const uint8_t> data) {
+  return [NSData dataWithBytes:data.data() length:data.size()];
+}
+}  // namespace
+
+FakeSystemInterface::FakeSystemInterface() = default;
+
+void FakeSystemInterface::set_auth_state(AuthState auth_state) {
+  auth_state_ = auth_state;
+}
+
+void FakeSystemInterface::set_next_auth_state(AuthState next_auth_state) {
+  next_auth_state_ = next_auth_state;
+}
+
+void FakeSystemInterface::SetMakeCredentialResult(
+    base::span<const uint8_t> attestation_object_bytes,
+    base::span<const uint8_t> credential_id) {
+  CHECK(!make_credential_error_code_);
+  make_credential_attestation_object_bytes_ =
+      fido_parsing_utils::Materialize(attestation_object_bytes);
+  make_credential_credential_id_ =
+      fido_parsing_utils::Materialize(credential_id);
+}
+
+void FakeSystemInterface::SetMakeCredentialError(int code) {
+  CHECK(!make_credential_attestation_object_bytes_);
+  make_credential_error_code_ = code;
+}
+
+void FakeSystemInterface::SetGetAssertionResult(
+    base::span<const uint8_t> authenticator_data,
+    base::span<const uint8_t> signature,
+    base::span<const uint8_t> user_id,
+    base::span<const uint8_t> credential_id) {
+  get_assertion_authenticator_data_ =
+      fido_parsing_utils::Materialize(authenticator_data);
+  get_assertion_signature_ = fido_parsing_utils::Materialize(signature);
+  get_assertion_user_id_ = fido_parsing_utils::Materialize(user_id);
+  get_assertion_credential_id_ = fido_parsing_utils::Materialize(credential_id);
+}
+
+void FakeSystemInterface::SetCredentials(
+    std::vector<DiscoverableCredentialMetadata> creds) {
+  creds_ = std::move(creds);
+}
+
+bool FakeSystemInterface::IsAvailable() const {
+  return true;
+}
+
+SystemInterface::AuthState FakeSystemInterface::GetAuthState() {
+  return auth_state_;
+}
+
+void FakeSystemInterface::AuthorizeAndContinue(
+    base::OnceCallback<void()> callback) {
+  CHECK(next_auth_state_.has_value());
+  auth_state_ = *next_auth_state_;
+  std::move(callback).Run();
+}
+
+void FakeSystemInterface::GetPlatformCredentials(
+    const std::string& rp_id,
+    void (^block)(
+        NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*)) {
+  NSMutableArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>* ret =
+      [[NSMutableArray alloc] init];
+  for (const auto& cred_values : creds_) {
+    // `init` on `ASAuthorizationWebBrowserPlatformPublicKeyCredential` is
+    // marked as `NS_UNAVAILABLE` and so is not called.
+    FakeBrowserPlatformPublicKeyCredential* cred =
+        [FakeBrowserPlatformPublicKeyCredential alloc];
+    cred.credentialID = ToNSData(cred_values.cred_id);
+    cred.userHandle = ToNSData(cred_values.user.id);
+    cred.relyingParty = base::SysUTF8ToNSString(rp_id);
+    cred.name = base::SysUTF8ToNSString(cred_values.user.name.value_or(""));
+    [ret addObject:cred];
+  }
+
+  block(ret);
+}
+
+void FakeSystemInterface::MakeCredential(
+    NSWindow* window,
+    CtapMakeCredentialRequest request,
+    base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+        callback) {
+  auto attestation_object_bytes =
+      std::move(make_credential_attestation_object_bytes_);
+  make_credential_attestation_object_bytes_.reset();
+  auto credential_id = std::move(make_credential_credential_id_);
+  make_credential_credential_id_.reset();
+  auto error_code = std::move(make_credential_error_code_);
+  make_credential_error_code_.reset();
+
+  if (error_code) {
+    std::move(callback).Run(nullptr,
+                            [[NSError alloc] initWithDomain:@"WKErrorDomain"
+                                                       code:error_code.value()
+                                                   userInfo:nullptr]);
+  } else if (!attestation_object_bytes) {
+    // Error code 1001 is a arbitrary number that doesn't trigger any special
+    // processing.
+    std::move(callback).Run(nullptr, [[NSError alloc] initWithDomain:@""
+                                                                code:1001
+                                                            userInfo:nullptr]);
+  } else {
+    FakeASAuthorizationPublicKeyCredentialRegistration* result =
+        [[FakeASAuthorizationPublicKeyCredentialRegistration alloc] init];
+    result.rawAttestationObject = ToNSData(*attestation_object_bytes);
+    result.credentialID = ToNSData(*credential_id);
+
+    FakeASAuthorization* authorization = [FakeASAuthorization alloc];
+    authorization.credential = result;
+
+    std::move(callback).Run(authorization, nullptr);
+  }
+}
+
+void FakeSystemInterface::GetAssertion(
+    NSWindow* window,
+    CtapGetAssertionRequest request,
+    base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+        callback) {
+  if (!get_assertion_authenticator_data_) {
+    std::move(callback).Run(nullptr, [[NSError alloc] initWithDomain:@""
+                                                                code:1001
+                                                            userInfo:nullptr]);
+    return;
+  }
+
+  FakeASAuthorizationPublicKeyCredentialAssertion* result =
+      [[FakeASAuthorizationPublicKeyCredentialAssertion alloc] init];
+  result.rawAuthenticatorData = ToNSData(*get_assertion_authenticator_data_);
+  result.signature = ToNSData(*get_assertion_signature_);
+  result.userID = ToNSData(*get_assertion_user_id_);
+  result.credentialID = ToNSData(*get_assertion_credential_id_);
+
+  get_assertion_authenticator_data_.reset();
+  get_assertion_signature_.reset();
+  get_assertion_user_id_.reset();
+  get_assertion_credential_id_.reset();
+
+  FakeASAuthorization* authorization = [FakeASAuthorization alloc];
+  authorization.credential = result;
+
+  std::move(callback).Run(authorization, nullptr);
+}
+
+FakeSystemInterface::~FakeSystemInterface() = default;
+
+}  // namespace device::fido::icloud_keychain
diff --git a/device/fido/mac/icloud_keychain.h b/device/fido/mac/icloud_keychain.h
new file mode 100644
index 0000000..d3c9217a
--- /dev/null
+++ b/device/fido/mac/icloud_keychain.h
@@ -0,0 +1,36 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_H_
+#define DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_H_
+
+#include <memory>
+#include "base/component_export.h"
+
+namespace device {
+
+class FidoDiscoveryBase;
+
+namespace fido::icloud_keychain {
+
+// IsSupported returns true if iCloud Keychain can be used. This is constant for
+// the lifetime of the process.
+COMPONENT_EXPORT(DEVICE_FIDO) bool IsSupported();
+
+// NewDiscovery returns a discovery that will immediately find an iCloud
+// Keychain authenticator. It is only valid to call this if `IsSupported`
+// returned true. It takes an `NSWindow*` to indicate the window which the
+// system UI would appear on top of. Since this header file can be included
+// by C++ code, the pointer is passed as an integer (see crbug.com/1433041).
+//
+// (By passing as a uintptr_t, the code assumes that the NSWindow has not
+// already been destroyed. This should be true since discovery objects are
+// created synchronously after getting the `BrowserWindow` of a `WebContents`.)
+COMPONENT_EXPORT(DEVICE_FIDO)
+std::unique_ptr<FidoDiscoveryBase> NewDiscovery(uintptr_t ns_window);
+
+}  // namespace fido::icloud_keychain
+}  // namespace device
+
+#endif  // DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_H_
diff --git a/device/fido/mac/icloud_keychain.mm b/device/fido/mac/icloud_keychain.mm
new file mode 100644
index 0000000..db3d886
--- /dev/null
+++ b/device/fido/mac/icloud_keychain.mm
@@ -0,0 +1,395 @@
+// 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 "device/fido/mac/icloud_keychain.h"
+
+#import <AuthenticationServices/ASFoundation.h>
+#import <AuthenticationServices/AuthenticationServices.h>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/ranges/algorithm.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/cbor/diagnostic_writer.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "components/device_event_log/device_event_log.h"
+#include "device/fido/attestation_object.h"
+#include "device/fido/attestation_statement.h"
+#include "device/fido/authenticator_data.h"
+#include "device/fido/ctap_get_assertion_request.h"
+#include "device/fido/ctap_make_credential_request.h"
+#include "device/fido/discoverable_credential_metadata.h"
+#include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_discovery_base.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "device/fido/fido_transport_protocol.h"
+#include "device/fido/mac/icloud_keychain_sys.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace device::fido::icloud_keychain {
+
+namespace {
+
+base::span<const uint8_t> ToSpan(NSData* data) {
+  return base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(data.bytes),
+                                   data.length);
+}
+
+std::vector<uint8_t> ToVector(NSData* data) {
+  const auto* p = reinterpret_cast<const uint8_t*>(data.bytes);
+  return std::vector<uint8_t>(p, p + data.length);
+}
+
+AuthenticatorSupportedOptions AuthenticatorOptions() {
+  AuthenticatorSupportedOptions options;
+  options.is_platform_device =
+      AuthenticatorSupportedOptions::PlatformDevice::kYes;
+  options.supports_resident_key = true;
+  options.user_verification_availability = AuthenticatorSupportedOptions::
+      UserVerificationAvailability::kSupportedAndConfigured;
+  options.supports_user_presence = true;
+  return options;
+}
+
+class API_AVAILABLE(macos(13.3)) Authenticator : public FidoAuthenticator {
+ public:
+  explicit Authenticator(NSWindow* window) : window_(window) {}
+  Authenticator(const Authenticator&) = delete;
+  Authenticator& operator=(const Authenticator&) = delete;
+
+  // FidoAuthenticator:
+  void InitializeAuthenticator(base::OnceClosure callback) override {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, std::move(callback));
+  }
+
+  void MakeCredential(CtapMakeCredentialRequest request,
+                      MakeCredentialOptions options,
+                      MakeCredentialCallback callback) override {
+    scoped_refptr<SystemInterface> sys_interface = GetSystemInterface();
+    auto continuation =
+        base::BindOnce(&Authenticator::OnMakeCredentialComplete,
+                       weak_factory_.GetWeakPtr(), std::move(callback));
+
+    // Authentication is not required for this operation, but it's a moment
+    // when we can reasonably ask for it. If the user authorizes Chromium then
+    // `platformCredentialsForRelyingParty` will start working.
+    switch (sys_interface->GetAuthState()) {
+      case SystemInterface::kAuthNotAuthorized:
+        FIDO_LOG(DEBUG) << "iCKC: requesting permission";
+        sys_interface->AuthorizeAndContinue(base::BindOnce(
+            &SystemInterface::MakeCredential, sys_interface, window_,
+            std::move(request), std::move(continuation)));
+        break;
+      case SystemInterface::kAuthDenied:
+        // The operation continues even if the user denied access. See above.
+        FIDO_LOG(DEBUG) << "iCKC: passkeys permission is denied";
+        [[fallthrough]];
+      case SystemInterface::kAuthAuthorized:
+        sys_interface->MakeCredential(window_, std::move(request),
+                                      std::move(continuation));
+        break;
+    }
+  }
+
+  void GetAssertion(CtapGetAssertionRequest request,
+                    CtapGetAssertionOptions options,
+                    GetAssertionCallback callback) override {
+    scoped_refptr<SystemInterface> sys_interface = GetSystemInterface();
+    auto continuation =
+        base::BindOnce(&Authenticator::OnGetAssertionComplete,
+                       weak_factory_.GetWeakPtr(), std::move(callback));
+
+    // Authentication is not required for this operation, but it's a moment
+    // when we can reasonably ask for it. If the user authorizes Chromium then
+    // `platformCredentialsForRelyingParty` will start working.
+    switch (sys_interface->GetAuthState()) {
+      case SystemInterface::kAuthNotAuthorized:
+        FIDO_LOG(DEBUG) << "iCKC: requesting permission";
+        sys_interface->AuthorizeAndContinue(base::BindOnce(
+            &SystemInterface::GetAssertion, GetSystemInterface(), window_,
+            std::move(request), std::move(continuation)));
+        break;
+      case SystemInterface::kAuthDenied:
+        // The operation continues even if the user denied access. See above.
+        FIDO_LOG(DEBUG) << "iCKC: passkeys permission is denied";
+        [[fallthrough]];
+      case SystemInterface::kAuthAuthorized:
+        GetSystemInterface()->GetAssertion(window_, std::move(request),
+                                           std::move(continuation));
+        break;
+    }
+  }
+
+  void GetPlatformCredentialInfoForRequest(
+      const CtapGetAssertionRequest& request,
+      const CtapGetAssertionOptions& options,
+      GetPlatformCredentialInfoForRequestCallback callback) override {
+    scoped_refptr<SystemInterface> sys_interface = GetSystemInterface();
+    switch (sys_interface->GetAuthState()) {
+      case SystemInterface::kAuthNotAuthorized:
+      case SystemInterface::kAuthDenied:
+        FIDO_LOG(DEBUG)
+            << "iCKC: cannot query credentials because of lack of permission";
+        std::move(callback).Run(
+            {}, FidoRequestHandlerBase::RecognizedCredential::kUnknown);
+        break;
+      case SystemInterface::kAuthAuthorized:
+        break;
+    }
+
+    scoped_refptr<base::SequencedTaskRunner> origin_task_runner =
+        base::SequencedTaskRunner::GetCurrentDefault();
+    __block auto internal_callback = std::move(callback);
+    const std::string rp_id = request.rp_id;
+    auto handler = ^(
+        NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*
+            credentials) {
+      std::vector<DiscoverableCredentialMetadata> ret;
+      for (NSUInteger i = 0; i < credentials.count; i++) {
+        const auto& cred = credentials[i];
+        ret.emplace_back(AuthenticatorType::kICloudKeychain, rp_id,
+                         ToVector(cred.credentialID),
+                         PublicKeyCredentialUserEntity(
+                             ToVector(cred.userHandle), cred.name.UTF8String,
+                             /* iCloud Keychain does not store
+                                a displayName for passkeys */
+                             absl::nullopt));
+      }
+      const auto has_credentials =
+          ret.empty() ? FidoRequestHandlerBase::RecognizedCredential::
+                            kNoRecognizedCredential
+                      : FidoRequestHandlerBase::RecognizedCredential::
+                            kHasRecognizedCredential;
+      origin_task_runner->PostTask(
+          FROM_HERE, base::BindOnce(std::move(internal_callback),
+                                    std::move(ret), has_credentials));
+    };
+    sys_interface->GetPlatformCredentials(rp_id, handler);
+  }
+
+  void Cancel() override {}
+
+  AuthenticatorType GetType() const override {
+    return AuthenticatorType::kICloudKeychain;
+  }
+
+  std::string GetId() const override { return "iCloudKeychain"; }
+
+  const AuthenticatorSupportedOptions& Options() const override {
+    static const AuthenticatorSupportedOptions options = AuthenticatorOptions();
+    return options;
+  }
+
+  absl::optional<FidoTransportProtocol> AuthenticatorTransport()
+      const override {
+    return FidoTransportProtocol::kInternal;
+  }
+
+  void GetTouch(base::OnceClosure callback) override {
+    NOTREACHED();
+    std::move(callback).Run();
+  }
+
+  base::WeakPtr<FidoAuthenticator> GetWeakPtr() override {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  void OnMakeCredentialComplete(MakeCredentialCallback callback,
+                                ASAuthorization* __strong authorization,
+                                NSError* __strong error) {
+    if (error) {
+      const std::string domain = base::SysNSStringToUTF8(error.domain);
+      FIDO_LOG(ERROR) << "iCKC: makeCredential failed, domain: " << domain
+                      << " code: " << error.code
+                      << " msg: " << error.localizedDescription.UTF8String;
+      if (domain == "WKErrorDomain" && error.code == 8) {
+        std::move(callback).Run(
+            CtapDeviceResponseCode::kCtap2ErrCredentialExcluded, absl::nullopt);
+      } else {
+        // All other errors are currently mapped to `kCtap2ErrOperationDenied`
+        // because it's not obvious that we want to differentiate them:
+        // https://developer.apple.com/documentation/authenticationservices/asauthorizationerror?language=objc
+        //
+        std::move(callback).Run(
+            CtapDeviceResponseCode::kCtap2ErrOperationDenied, absl::nullopt);
+      }
+      return;
+    }
+
+    FIDO_LOG(DEBUG) << "iCKC: makeCredential completed";
+    CHECK([authorization.credential
+        conformsToProtocol:
+            @protocol(ASAuthorizationPublicKeyCredentialRegistration)]);
+    id<ASAuthorizationPublicKeyCredentialRegistration> result =
+        (id<ASAuthorizationPublicKeyCredentialRegistration>)
+            authorization.credential;
+
+    absl::optional<cbor::Value> attestation_object_value =
+        cbor::Reader::Read(ToSpan(result.rawAttestationObject));
+    if (!attestation_object_value || !attestation_object_value->is_map()) {
+      FIDO_LOG(ERROR) << "iCKC: failed to parse attestation CBOR";
+      std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                              absl::nullopt);
+      return;
+    }
+
+    absl::optional<AttestationObject> attestation_object =
+        AttestationObject::Parse(*attestation_object_value);
+    if (!attestation_object) {
+      FIDO_LOG(ERROR) << "iCKC: failed to parse attestation object";
+      std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                              absl::nullopt);
+      return;
+    }
+
+    AuthenticatorMakeCredentialResponse response(
+        FidoTransportProtocol::kInternal, std::move(*attestation_object));
+
+    std::vector<uint8_t> credential_id_from_auth_data =
+        response.attestation_object.authenticator_data().GetCredentialId();
+    base::span<const uint8_t> credential_id = ToSpan(result.credentialID);
+    if (!base::ranges::equal(credential_id_from_auth_data, credential_id)) {
+      FIDO_LOG(ERROR) << "iCKC: credential ID mismatch: "
+                      << base::HexEncode(credential_id_from_auth_data) << " vs "
+                      << base::HexEncode(credential_id);
+      std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                              absl::nullopt);
+      return;
+    }
+
+    response.is_resident_key = true;
+    response.transports.emplace();
+    response.transports->insert(FidoTransportProtocol::kHybrid);
+    response.transports->insert(FidoTransportProtocol::kInternal);
+    response.transport_used = FidoTransportProtocol::kInternal;
+
+    std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
+                            std::move(response));
+  }
+
+  void OnGetAssertionComplete(GetAssertionCallback callback,
+                              ASAuthorization* __strong authorization,
+                              NSError* __strong error) {
+    if (error) {
+      FIDO_LOG(ERROR) << "iCKC: getAssertion failed, domain: "
+                      << base::SysNSStringToUTF8(error.domain)
+                      << " code: " << error.code
+                      << " msg: " << error.localizedDescription.UTF8String;
+      // All errors are currently mapped to `kCtap2ErrOperationDenied` because
+      // it's not obvious that we want to differentiate them:
+      // https://developer.apple.com/documentation/authenticationservices/asauthorizationerror?language=objc
+      std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied,
+                              {});
+      return;
+    }
+
+    FIDO_LOG(DEBUG) << "iCKC: getAssertion completed";
+    CHECK([authorization.credential
+        conformsToProtocol:@protocol(
+                               ASAuthorizationPublicKeyCredentialAssertion)]);
+    id<ASAuthorizationPublicKeyCredentialAssertion> result =
+        (id<ASAuthorizationPublicKeyCredentialAssertion>)
+            authorization.credential;
+
+    absl::optional<AuthenticatorData> authenticator_data =
+        AuthenticatorData::DecodeAuthenticatorData(
+            ToSpan(result.rawAuthenticatorData));
+    if (!authenticator_data) {
+      FIDO_LOG(ERROR) << "iCKC: invalid authData";
+      std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrOther, {});
+      return;
+    }
+
+    AuthenticatorGetAssertionResponse response(
+        std::move(*authenticator_data),
+        fido_parsing_utils::Materialize(ToSpan(result.signature)));
+    response.user_entity = PublicKeyCredentialUserEntity(
+        fido_parsing_utils::Materialize(ToSpan(result.userID)));
+    response.credential = PublicKeyCredentialDescriptor(
+        CredentialType::kPublicKey,
+        fido_parsing_utils::Materialize(ToSpan(result.credentialID)));
+    response.user_selected = true;
+    // The hybrid flow can be offered in the macOS UI, so this may be
+    // incorrect, but we've no way of knowing. It's not clear that we can
+    // do much about this with the macOS API at the time of writing, short of
+    // replacing the system UI completely.
+    response.transport_used = FidoTransportProtocol::kInternal;
+
+    std::vector<AuthenticatorGetAssertionResponse> responses;
+    responses.emplace_back(std::move(response));
+    std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
+                            std::move(responses));
+  }
+
+  NSWindow* const window_;
+  base::WeakPtrFactory<Authenticator> weak_factory_{this};
+};
+
+class API_AVAILABLE(macos(13.3)) Discovery : public FidoDiscoveryBase {
+ public:
+  explicit Discovery(NSWindow* window)
+      : FidoDiscoveryBase(FidoTransportProtocol::kInternal), window_(window) {}
+
+  // FidoDiscoveryBase:
+  void Start() override {
+    if (!observer()) {
+      return;
+    }
+
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(&Discovery::AddAuthenticator,
+                                  weak_factory_.GetWeakPtr()));
+  }
+
+ private:
+  void AddAuthenticator() {
+    authenticator_ = std::make_unique<Authenticator>(window_);
+    observer()->DiscoveryStarted(this, /*success=*/true,
+                                 {authenticator_.get()});
+  }
+
+  NSWindow* const window_;
+  std::unique_ptr<Authenticator> authenticator_;
+  base::WeakPtrFactory<Discovery> weak_factory_{this};
+};
+
+}  // namespace
+
+bool IsSupported() {
+  if (@available(macOS 13.3, *)) {
+    return GetSystemInterface()->IsAvailable();
+  }
+  return false;
+}
+
+std::unique_ptr<FidoDiscoveryBase> NewDiscovery(uintptr_t ns_window) {
+  if (@available(macOS 13.3, *)) {
+    NSWindow* window;
+    static_assert(sizeof(window) == sizeof(ns_window));
+    memcpy((void*)&window, &ns_window, sizeof(ns_window));
+
+    auto discovery = std::make_unique<Discovery>(window);
+
+    // Clear pointer so that ObjC doesn't try to release it.
+    memset((void*)&window, 0, sizeof(window));
+
+    return discovery;
+  }
+
+  NOTREACHED();
+  return nullptr;
+}
+
+}  // namespace device::fido::icloud_keychain
diff --git a/device/fido/mac/icloud_keychain_internals.h b/device/fido/mac/icloud_keychain_internals.h
new file mode 100644
index 0000000..e4acca5
--- /dev/null
+++ b/device/fido/mac/icloud_keychain_internals.h
@@ -0,0 +1,55 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_INTERNALS_H_
+#define DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_INTERNALS_H_
+
+NS_ASSUME_NONNULL_BEGIN
+
+// The following definitions of ASC* interfaces are from
+// AuthenticationServicesCore, which is a private framework. The full
+// definitions can be found in
+// Source/WebKit/Platform/spi/Cocoa/AuthenticationServicesCoreSPI.h from
+// WebKit, but only the needed parts are specified here.
+//
+// These interfaces are needed to implement several behaviours that browsers
+// require. Most importantly, specifying the full clientDataHash rather than
+// the challenge.
+
+@interface ASCPublicKeyCredentialDescriptor : NSObject <NSSecureCoding>
+- (instancetype)initWithCredentialID:(NSData*)credentialID
+                          transports:
+                              (nullable NSArray<NSString*>*)allowedTransports;
+@end
+
+@protocol ASCPublicKeyCredentialCreationOptions
+@property(nonatomic, copy) NSData* clientDataHash;
+@property(nonatomic, nullable, copy) NSData* challenge;
+@property(nonatomic, nullable, copy) NSString* userVerificationPreference;
+@property(nonatomic, copy) NSArray<NSNumber*>* supportedAlgorithmIdentifiers;
+@property(nonatomic) BOOL shouldRequireResidentKey;
+@property(nonatomic, copy)
+    NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials;
+@end
+
+@protocol ASCPublicKeyCredentialAssertionOptions <NSCopying>
+@property(nonatomic, copy) NSData* clientDataHash;
+@end
+
+@protocol ASCCredentialRequestContext
+@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
+    platformKeyCredentialAssertionOptions;
+@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
+    platformKeyCredentialCreationOptions;
+@end
+
+@interface ASAuthorizationController (Secrets)
+- (id<ASCCredentialRequestContext>)
+    _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
+                          error:(NSError**)outError;
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif  // DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_INTERNALS_H_
diff --git a/device/fido/mac/icloud_keychain_sys.h b/device/fido/mac/icloud_keychain_sys.h
new file mode 100644
index 0000000..9b125c2
--- /dev/null
+++ b/device/fido/mac/icloud_keychain_sys.h
@@ -0,0 +1,90 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_SYS_H_
+#define DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_SYS_H_
+
+#include <memory>
+
+#import <AuthenticationServices/AuthenticationServices.h>
+#import <Foundation/Foundation.h>
+
+#include "base/component_export.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "device/fido/ctap_get_assertion_request.h"
+#include "device/fido/ctap_make_credential_request.h"
+
+#if !defined(__OBJC__) || !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@class NSWindow;
+
+namespace device::fido::icloud_keychain {
+
+// SystemInterface is the lowest-level abstraction for iCloud Keychain support.
+// Automated tests can't reach below this point without triggering dialogs on
+// the machine.
+class COMPONENT_EXPORT(DEVICE_FIDO) API_AVAILABLE(macos(13.3)) SystemInterface
+    : public base::RefCounted<SystemInterface> {
+ public:
+  // IsAvailable returns true if the other functions in this interface can be
+  // called.
+  virtual bool IsAvailable() const = 0;
+
+  // These names are extremely long and so are aliased here to make other code
+  // a bit more readable.
+  using AuthState =
+      ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState;
+  static constexpr auto kAuthNotAuthorized =
+      ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateNotDetermined;
+  static constexpr auto kAuthDenied =
+      ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateDenied;
+  static constexpr auto kAuthAuthorized =
+      ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateAuthorized;
+
+  // GetAuthState returns the current level of authorization for calling
+  // `GetPlatformCredentials`.
+  virtual AuthState GetAuthState() = 0;
+
+  // AuthorizeAndContinue requests authorization for calling
+  // `GetPlatformCredentials` and, successful or not, then calls `callback`.
+  virtual void AuthorizeAndContinue(base::OnceCallback<void()> callback) = 0;
+
+  // GetPlatformCredentials enumerates the credentials for `rp_id`.
+  virtual void GetPlatformCredentials(
+      const std::string& rp_id,
+      void (^)(
+          NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*)) = 0;
+
+  virtual void MakeCredential(
+      NSWindow* window,
+      CtapMakeCredentialRequest request,
+      base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+          callback) = 0;
+
+  virtual void GetAssertion(
+      NSWindow* window,
+      CtapGetAssertionRequest request,
+      base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+          callback) = 0;
+
+ protected:
+  friend class base::RefCounted<SystemInterface>;
+  virtual ~SystemInterface();
+};
+
+// GetSystemInterface returns the current implementation of `SystemInterface`.
+COMPONENT_EXPORT(DEVICE_FIDO)
+API_AVAILABLE(macos(13.3)) scoped_refptr<SystemInterface> GetSystemInterface();
+
+COMPONENT_EXPORT(DEVICE_FIDO)
+API_AVAILABLE(macos(13.3))
+void SetSystemInterfaceForTesting(scoped_refptr<SystemInterface> sys_interface);
+
+}  // namespace device::fido::icloud_keychain
+
+#endif  // DEVICE_FIDO_MAC_ICLOUD_KEYCHAIN_SYS_H_
diff --git a/device/fido/mac/icloud_keychain_sys.mm b/device/fido/mac/icloud_keychain_sys.mm
new file mode 100644
index 0000000..ed6e4dee
--- /dev/null
+++ b/device/fido/mac/icloud_keychain_sys.mm
@@ -0,0 +1,394 @@
+// 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 "device/fido/mac/icloud_keychain_sys.h"
+
+#import <AuthenticationServices/AuthenticationServices.h>
+
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/device_event_log/device_event_log.h"
+#include "device/fido/mac/icloud_keychain_internals.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// This function is needed by the interfaces below, but interfaces must be
+// in the top-level namespace.
+NSData* ToNSData(base::span<const uint8_t> data) {
+  return [NSData dataWithBytes:data.data() length:data.size()];
+}
+
+}  // namespace
+
+// ICloudKeychainPresentationDelegate simply returns an `NSWindow` when asked by
+// an `ASAuthorizationController`.
+API_AVAILABLE(macos(13.3))
+@interface ICloudKeychainPresentationDelegate
+    : NSObject <ASAuthorizationControllerPresentationContextProviding>
+@property(nonatomic, strong) NSWindow* window;
+@end
+
+@implementation ICloudKeychainPresentationDelegate
+@synthesize window = _window;
+
+- (ASPresentationAnchor)presentationAnchorForAuthorizationController:
+    (ASAuthorizationController*)controller {
+  return _window;
+}
+@end
+
+// ICloudKeychainDelegate receives callbacks when an `ASAuthorizationController`
+// operation completes (successfully or otherwise) and bridges to a
+// `OnceCallback`.
+API_AVAILABLE(macos(13.3))
+@interface ICloudKeychainDelegate : NSObject <ASAuthorizationControllerDelegate>
+- (void)setCallback:
+    (base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>)
+        callback;
+- (void)setCleanupCallback:(base::OnceClosure)callback;
+@end
+
+@implementation ICloudKeychainDelegate {
+  base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+      _callback;
+  base::OnceClosure _cleanupCallback;
+}
+
+- (void)setCallback:
+    (base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>)
+        callback {
+  _callback = std::move(callback);
+}
+
+- (void)setCleanupCallback:(base::OnceClosure)callback {
+  _cleanupCallback = std::move(callback);
+}
+
+- (void)authorizationController:(ASAuthorizationController*)controller
+    didCompleteWithAuthorization:(ASAuthorization*)authorization {
+  std::move(_callback).Run(authorization, nullptr);
+  std::move(_cleanupCallback).Run();
+}
+
+- (void)authorizationController:(ASAuthorizationController*)controller
+           didCompleteWithError:(NSError*)error {
+  std::move(_callback).Run(nullptr, error);
+  std::move(_cleanupCallback).Run();
+}
+@end
+
+// ICloudKeychainCreateController overrides `_requestContextWithRequests` from
+// `ASAuthorizationController` so that various extra parameters, which browsers
+// need to set, can be specified.
+API_AVAILABLE(macos(13.3))
+@interface ICloudKeychainCreateController : ASAuthorizationController
+@end
+
+@implementation ICloudKeychainCreateController {
+  absl::optional<device::CtapMakeCredentialRequest> request_;
+}
+
+- (void)setRequest:(device::CtapMakeCredentialRequest)request {
+  request_ = std::move(request);
+}
+
+- (id<ASCCredentialRequestContext>)
+    _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
+                          error:(NSError**)outError {
+  id<ASCCredentialRequestContext> context =
+      [super _requestContextWithRequests:requests error:outError];
+
+  id<ASCPublicKeyCredentialCreationOptions> registrationOptions =
+      context.platformKeyCredentialCreationOptions;
+  registrationOptions.clientDataHash = ToNSData(request_->client_data_hash);
+  registrationOptions.challenge = nil;
+
+  NSMutableArray<NSNumber*>* supported_algos = [[NSMutableArray alloc] init];
+  for (const device::PublicKeyCredentialParams::CredentialInfo& param :
+       request_->public_key_credential_params.public_key_credential_params()) {
+    if (param.type == device::CredentialType::kPublicKey) {
+      [supported_algos addObject:[NSNumber numberWithInt:base::strict_cast<int>(
+                                                             param.algorithm)]];
+    }
+  }
+  if ([supported_algos count] > 0) {
+    registrationOptions.supportedAlgorithmIdentifiers = supported_algos;
+  }
+
+  registrationOptions.shouldRequireResidentKey =
+      request_->resident_key_required;
+
+  const Class descriptor_class =
+      NSClassFromString(@"ASCPublicKeyCredentialDescriptor");
+  NSMutableArray<ASCPublicKeyCredentialDescriptor*>* exclude_list =
+      [[NSMutableArray alloc] init];
+  for (const auto& cred : request_->exclude_list) {
+    if (cred.credential_type != device::CredentialType::kPublicKey) {
+      continue;
+    }
+    NSMutableArray<NSString*>* transports = [[NSMutableArray alloc] init];
+    for (const auto transport : cred.transports) {
+      [transports addObject:base::SysUTF8ToNSString(ToString(transport))];
+    }
+    ASCPublicKeyCredentialDescriptor* descriptor =
+        [[descriptor_class alloc] initWithCredentialID:ToNSData(cred.id)
+                                            transports:transports];
+    [exclude_list addObject:descriptor];
+  }
+  if ([exclude_list count] > 0) {
+    registrationOptions.excludedCredentials = exclude_list;
+  }
+
+  return context;
+}
+@end
+
+// ICloudKeychainGetController overrides `_requestContextWithRequests` from
+// `ASAuthorizationController` so that various extra parameters, which browsers
+// need to set, can be specified.
+API_AVAILABLE(macos(13.3))
+@interface ICloudKeychainGetController : ASAuthorizationController
+@end
+
+@implementation ICloudKeychainGetController {
+  absl::optional<device::CtapGetAssertionRequest> request_;
+}
+
+- (void)setRequest:(device::CtapGetAssertionRequest)request {
+  request_ = std::move(request);
+}
+
+- (id<ASCCredentialRequestContext>)
+    _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
+                          error:(NSError**)outError {
+  id<ASCCredentialRequestContext> context =
+      [super _requestContextWithRequests:requests error:outError];
+
+  id<ASCPublicKeyCredentialAssertionOptions> assertionOptions =
+      context.platformKeyCredentialAssertionOptions;
+  assertionOptions.clientDataHash = ToNSData(request_->client_data_hash);
+  context.platformKeyCredentialAssertionOptions =
+      [assertionOptions copyWithZone:nil];
+  return context;
+}
+@end
+
+namespace device::fido::icloud_keychain {
+namespace {
+
+API_AVAILABLE(macos(13.3))
+ASAuthorizationWebBrowserPublicKeyCredentialManager* GetManager() {
+  return [[ASAuthorizationWebBrowserPublicKeyCredentialManager alloc] init];
+}
+
+bool ProcessHasEntitlement() {
+  base::ScopedCFTypeRef<SecTaskRef> task(SecTaskCreateFromSelf(nullptr));
+  if (!task) {
+    return false;
+  }
+
+  base::ScopedCFTypeRef<CFTypeRef> entitlement_value_cftype(
+      SecTaskCopyValueForEntitlement(
+          task, CFSTR("com.apple.developer.web-browser.public-key-credential"),
+          nullptr));
+  return entitlement_value_cftype;
+}
+
+API_AVAILABLE(macos(13.3))
+ASAuthorizationPublicKeyCredentialAttestationKind Convert(
+    AttestationConveyancePreference preference) {
+  // If attestation is requested then the request immediately fails, so
+  // all types are mapped to `none`.
+  return ASAuthorizationPublicKeyCredentialAttestationKindNone;
+}
+
+API_AVAILABLE(macos(13.3))
+ASAuthorizationPublicKeyCredentialUserVerificationPreference Convert(
+    UserVerificationRequirement uv) {
+  switch (uv) {
+    case UserVerificationRequirement::kDiscouraged:
+      return ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged;
+    case UserVerificationRequirement::kPreferred:
+      return ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred;
+    case UserVerificationRequirement::kRequired:
+      return ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired;
+  }
+}
+
+class API_AVAILABLE(macos(13.3)) NativeSystemInterface
+    : public SystemInterface {
+ public:
+  bool IsAvailable() const override {
+    static bool available = ProcessHasEntitlement();
+    return available;
+  }
+
+  AuthState GetAuthState() override {
+    return GetManager().authorizationStateForPlatformCredentials;
+  }
+
+  void AuthorizeAndContinue(base::OnceCallback<void()> callback) override {
+    auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+    __block auto internal_callback = std::move(callback);
+    [GetManager()
+        requestAuthorizationForPublicKeyCredentials:^(AuthState state) {
+          task_runner->PostTask(FROM_HERE,
+                                base::BindOnce(std::move(internal_callback)));
+        }];
+  }
+
+  void GetPlatformCredentials(
+      const std::string& rp_id,
+      void (^handler)(
+          NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*))
+      override {
+    [GetManager()
+        platformCredentialsForRelyingParty:base::SysUTF8ToNSString(rp_id)
+                         completionHandler:handler];
+  }
+
+  void MakeCredential(
+      NSWindow* window,
+      CtapMakeCredentialRequest request,
+      base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+          callback) override {
+    DCHECK(!create_controller_);
+    DCHECK(!get_controller_);
+    DCHECK(!delegate_);
+    DCHECK(!presentation_delegate_);
+
+    ASAuthorizationPlatformPublicKeyCredentialProvider* provider =
+        [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
+            initWithRelyingPartyIdentifier:base::SysUTF8ToNSString(
+                                               request.rp.id)];
+    NSData* challenge = ToNSData(request.client_data_hash);
+    NSData* user_id = ToNSData(request.user.id);
+    NSString* name = base::SysUTF8ToNSString(request.user.name.value_or(""));
+    ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest*
+        create_request =
+            [provider createCredentialRegistrationRequestWithChallenge:challenge
+                                                                  name:name
+                                                                userID:user_id];
+    create_request.attestationPreference =
+        Convert(request.attestation_preference);
+    create_request.userVerificationPreference =
+        Convert(request.user_verification);
+    if (request.user.display_name) {
+      create_request.displayName =
+          base::SysUTF8ToNSString(*request.user.display_name);
+    }
+
+    create_controller_ = [[ICloudKeychainCreateController alloc]
+        initWithAuthorizationRequests:@[ create_request ]];
+    [create_controller_ setRequest:std::move(request)];
+    delegate_ = [[ICloudKeychainDelegate alloc] init];
+    [delegate_ setCallback:std::move(callback)];
+    [delegate_ setCleanupCallback:base::BindOnce(
+                                      &NativeSystemInterface::Cleanup, this)];
+    create_controller_.delegate = delegate_;
+    presentation_delegate_ = [[ICloudKeychainPresentationDelegate alloc] init];
+    presentation_delegate_.window = window;
+    create_controller_.presentationContextProvider = presentation_delegate_;
+
+    [create_controller_ performRequests];
+  }
+
+  void GetAssertion(
+      NSWindow* window,
+      CtapGetAssertionRequest request,
+      base::OnceCallback<void(ASAuthorization* __strong, NSError* __strong)>
+          callback) override {
+    DCHECK(!create_controller_);
+    DCHECK(!get_controller_);
+    DCHECK(!delegate_);
+    DCHECK(!presentation_delegate_);
+
+    ASAuthorizationPlatformPublicKeyCredentialProvider* provider =
+        [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
+            initWithRelyingPartyIdentifier:base::SysUTF8ToNSString(
+                                               request.rp_id)];
+
+    NSData* challenge = ToNSData(request.client_data_hash);
+    ASAuthorizationPlatformPublicKeyCredentialAssertionRequest* get_request =
+        [provider createCredentialAssertionRequestWithChallenge:challenge];
+    NSMutableArray* allowedCredentials = [[NSMutableArray alloc] init];
+    for (const auto& cred : request.allow_list) {
+      // All credentials are assumed to be platform credentials because we don't
+      // wish to trigger macOS's handling of security keys.
+      [allowedCredentials
+          addObject:[[ASAuthorizationPlatformPublicKeyCredentialDescriptor
+                        alloc] initWithCredentialID:ToNSData(cred.id)]];
+    }
+    get_request.allowedCredentials = allowedCredentials;
+    get_request.userVerificationPreference = Convert(request.user_verification);
+    get_controller_ = [[ICloudKeychainGetController alloc]
+        initWithAuthorizationRequests:@[ get_request ]];
+    [get_controller_ setRequest:std::move(request)];
+    delegate_ = [[ICloudKeychainDelegate alloc] init];
+    [delegate_ setCallback:std::move(callback)];
+    [delegate_ setCleanupCallback:base::BindOnce(
+                                      &NativeSystemInterface::Cleanup, this)];
+    get_controller_.delegate = delegate_;
+    presentation_delegate_ = [[ICloudKeychainPresentationDelegate alloc] init];
+    presentation_delegate_.window = window;
+    get_controller_.presentationContextProvider = presentation_delegate_;
+
+    [get_controller_ performRequests];
+  }
+
+ protected:
+  ~NativeSystemInterface() override = default;
+
+  void Cleanup() {
+    create_controller_ = nullptr;
+    get_controller_ = nullptr;
+    delegate_ = nullptr;
+    presentation_delegate_ = nullptr;
+  }
+
+  ICloudKeychainCreateController* __strong create_controller_;
+  ICloudKeychainGetController* __strong get_controller_;
+  ICloudKeychainDelegate* __strong delegate_;
+  ICloudKeychainPresentationDelegate* __strong presentation_delegate_;
+};
+
+API_AVAILABLE(macos(13.3))
+scoped_refptr<SystemInterface> GetNativeSystemInterface() {
+  static scoped_refptr<SystemInterface> native_sys_interface =
+      base::MakeRefCounted<NativeSystemInterface>();
+  return native_sys_interface;
+}
+
+API_AVAILABLE(macos(13.3))
+scoped_refptr<SystemInterface>& GetTestInterface() {
+  static scoped_refptr<SystemInterface> test_interface;
+  return test_interface;
+}
+
+}  // namespace
+
+SystemInterface::~SystemInterface() = default;
+
+API_AVAILABLE(macos(13.3))
+scoped_refptr<SystemInterface> GetSystemInterface() {
+  scoped_refptr<SystemInterface>& test_interface = GetTestInterface();
+  if (test_interface) {
+    return test_interface;
+  }
+  return GetNativeSystemInterface();
+}
+
+API_AVAILABLE(macos(13.3))
+void SetSystemInterfaceForTesting(  // IN-TEST
+    scoped_refptr<SystemInterface> sys_interface) {
+  GetTestInterface() = sys_interface;
+}
+
+}  // namespace device::fido::icloud_keychain
diff --git a/device/fido/mac/icloud_keychain_unittest.mm b/device/fido/mac/icloud_keychain_unittest.mm
new file mode 100644
index 0000000..9120679d
--- /dev/null
+++ b/device/fido/mac/icloud_keychain_unittest.mm
@@ -0,0 +1,412 @@
+// 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 "device/fido/mac/icloud_keychain.h"
+#include "base/logging.h"
+#include "base/ranges/algorithm.h"
+#include "base/test/task_environment.h"
+#include "device/fido/discoverable_credential_metadata.h"
+#include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_discovery_base.h"
+#include "device/fido/fido_transport_protocol.h"
+#include "device/fido/mac/fake_icloud_keychain_sys.h"
+#include "device/fido/test_callback_receiver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace device::fido::icloud_keychain {
+
+namespace {
+
+static const uint8_t kAttestationObjectBytes[] = {
+    0xa3, 0x63, 0x66, 0x6d, 0x74, 0x66, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64,
+    0x67, 0x61, 0x74, 0x74, 0x53, 0x74, 0x6d, 0x74, 0xa3, 0x63, 0x61, 0x6c,
+    0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x58, 0x46, 0x30, 0x44, 0x02, 0x20,
+    0x05, 0xaa, 0x7b, 0xcb, 0x4f, 0x15, 0xc8, 0x3d, 0x3a, 0x0b, 0x57, 0x12,
+    0xa8, 0xab, 0x8d, 0x60, 0x16, 0x9b, 0xfb, 0x91, 0x91, 0xfd, 0x1d, 0xe6,
+    0x30, 0xab, 0xae, 0xe3, 0x71, 0xd6, 0xfb, 0x33, 0x02, 0x20, 0x30, 0xba,
+    0x47, 0x7b, 0x38, 0x06, 0x89, 0xbc, 0x46, 0x1c, 0xa1, 0x60, 0x7e, 0x99,
+    0x88, 0x85, 0x7f, 0x24, 0xf9, 0x82, 0xb7, 0xb5, 0x03, 0x8f, 0x92, 0x16,
+    0x86, 0xd6, 0x10, 0x50, 0x9c, 0xc8, 0x63, 0x78, 0x35, 0x63, 0x81, 0x59,
+    0x02, 0xdc, 0x30, 0x82, 0x02, 0xd8, 0x30, 0x82, 0x01, 0xc0, 0xa0, 0x03,
+    0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0xff, 0x87, 0x6c, 0x2d, 0xaf, 0x73,
+    0x79, 0xc8, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+    0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a, 0x06,
+    0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f,
+    0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+    0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32,
+    0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x34, 0x30,
+    0x38, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f,
+    0x32, 0x30, 0x35, 0x30, 0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30,
+    0x30, 0x30, 0x5a, 0x30, 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+    0x04, 0x06, 0x13, 0x02, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03,
+    0x55, 0x04, 0x0a, 0x0c, 0x09, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20,
+    0x41, 0x42, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c,
+    0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
+    0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
+    0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+    0x1e, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20,
+    0x45, 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x37, 0x36,
+    0x32, 0x30, 0x38, 0x37, 0x34, 0x32, 0x33, 0x30, 0x59, 0x30, 0x13, 0x06,
+    0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86,
+    0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x25, 0xf1,
+    0x23, 0xa0, 0x48, 0x28, 0x3f, 0xc5, 0x79, 0x6c, 0xcf, 0x88, 0x7d, 0x99,
+    0x48, 0x9f, 0xd9, 0x35, 0xc2, 0x41, 0x98, 0xc4, 0xb5, 0xd8, 0xd5, 0xb2,
+    0xc2, 0xbf, 0xd7, 0xdd, 0x5d, 0x15, 0xaf, 0xe4, 0x5b, 0x70, 0x70, 0x77,
+    0x65, 0x67, 0xd5, 0xb5, 0xb0, 0xb2, 0x3e, 0x04, 0x56, 0x0b, 0x5b, 0xea,
+    0x77, 0xb4, 0x83, 0xb1, 0xf6, 0x49, 0x1e, 0x53, 0xa3, 0xf2, 0xbe, 0xe6,
+    0xa3, 0x9a, 0xa3, 0x81, 0x81, 0x30, 0x7f, 0x30, 0x13, 0x06, 0x0a, 0x2b,
+    0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x0d, 0x01, 0x04, 0x05, 0x04,
+    0x03, 0x05, 0x05, 0x06, 0x30, 0x22, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04,
+    0x01, 0x82, 0xc4, 0x0a, 0x02, 0x04, 0x15, 0x31, 0x2e, 0x33, 0x2e, 0x36,
+    0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34, 0x38, 0x32,
+    0x2e, 0x31, 0x2e, 0x39, 0x30, 0x13, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04,
+    0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x05,
+    0x20, 0x30, 0x21, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5,
+    0x1c, 0x01, 0x01, 0x04, 0x04, 0x12, 0x04, 0x10, 0xd8, 0x52, 0x2d, 0x9f,
+    0x57, 0x5b, 0x48, 0x66, 0x88, 0xa9, 0xba, 0x99, 0xfa, 0x02, 0xf3, 0x5b,
+    0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02,
+    0x30, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+    0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x52, 0xb0,
+    0x69, 0x49, 0xdb, 0xaa, 0xd1, 0xa6, 0x4c, 0x1b, 0xa9, 0xeb, 0xc1, 0x98,
+    0xb3, 0x17, 0xec, 0x31, 0xf9, 0xa3, 0x73, 0x63, 0xba, 0x51, 0x61, 0xb3,
+    0x42, 0xe3, 0xa4, 0x9c, 0xad, 0x50, 0x4f, 0x34, 0xe7, 0x42, 0x8b, 0xb8,
+    0x96, 0xe9, 0xcf, 0xd2, 0x8d, 0x03, 0xad, 0x10, 0xce, 0x32, 0x5a, 0x06,
+    0x83, 0x8e, 0x9b, 0x6c, 0x4e, 0xcb, 0x17, 0xad, 0x40, 0xd0, 0x90, 0xa1,
+    0x6c, 0x9e, 0x7c, 0x34, 0x49, 0x83, 0x32, 0xff, 0x85, 0x3b, 0x62, 0x74,
+    0x7e, 0x8f, 0xcd, 0xf0, 0x0d, 0xae, 0x62, 0x75, 0x6e, 0x57, 0xbd, 0x40,
+    0xb1, 0x6d, 0x67, 0x79, 0x07, 0xa8, 0x35, 0xc0, 0x43, 0x5a, 0x2e, 0xbc,
+    0xe9, 0xb0, 0xb9, 0x06, 0x9c, 0xa1, 0x22, 0xbf, 0x9d, 0x96, 0x4a, 0x73,
+    0x20, 0x6a, 0xf7, 0x4f, 0xf3, 0xc0, 0x01, 0x44, 0xeb, 0xff, 0x3d, 0xe7,
+    0xc7, 0x75, 0x8d, 0x31, 0x47, 0xc8, 0xc2, 0xf9, 0xfe, 0x87, 0xc1, 0x2f,
+    0x2a, 0x96, 0x75, 0xa2, 0x04, 0x6b, 0x01, 0x07, 0x63, 0x61, 0xa9, 0x97,
+    0x21, 0x87, 0x1f, 0xa7, 0x8f, 0xb0, 0xde, 0x29, 0x45, 0xb5, 0x79, 0xf9,
+    0x16, 0x6c, 0x48, 0xad, 0x2f, 0xd5, 0x0c, 0x3c, 0xe5, 0x6c, 0x82, 0x21,
+    0xa7, 0x50, 0x83, 0xf6, 0x56, 0x11, 0x93, 0x94, 0x36, 0x8f, 0xf1, 0x7d,
+    0x2c, 0x92, 0x0c, 0x63, 0xa0, 0x9f, 0x01, 0xed, 0x25, 0x01, 0x14, 0x6b,
+    0x7d, 0xf1, 0xab, 0x39, 0x70, 0xa2, 0xa3, 0x29, 0x38, 0xfa, 0x9a, 0x51,
+    0x7a, 0xf4, 0x71, 0x08, 0x5e, 0x16, 0x0b, 0x3c, 0xa7, 0x97, 0x64, 0x23,
+    0x17, 0x46, 0xba, 0x6a, 0xbb, 0xa6, 0x8e, 0x0d, 0x13, 0xce, 0x25, 0x97,
+    0x96, 0xbc, 0xd2, 0xa0, 0x3a, 0xd8, 0x3c, 0x74, 0xe1, 0x53, 0x31, 0x32,
+    0x8e, 0xab, 0x43, 0x8e, 0x6a, 0x41, 0x97, 0xcb, 0x12, 0xec, 0x6f, 0xd1,
+    0xe3, 0x88, 0x68, 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x58,
+    0xc4, 0x26, 0xbd, 0x72, 0x78, 0xbe, 0x46, 0x37, 0x61, 0xf1, 0xfa, 0xa1,
+    0xb1, 0x0a, 0xb4, 0xc4, 0xf8, 0x26, 0x70, 0x26, 0x9c, 0x41, 0x0c, 0x72,
+    0x6a, 0x1f, 0xd6, 0xe0, 0x58, 0x55, 0xe1, 0x9b, 0x46, 0x45, 0x00, 0x00,
+    0x00, 0x03, 0xd8, 0x52, 0x2d, 0x9f, 0x57, 0x5b, 0x48, 0x66, 0x88, 0xa9,
+    0xba, 0x99, 0xfa, 0x02, 0xf3, 0x5b, 0x00, 0x40, 0x91, 0x88, 0xee, 0xf6,
+    0xe9, 0x75, 0xef, 0x4e, 0x8b, 0x5b, 0x91, 0x34, 0xbf, 0x59, 0x89, 0x37,
+    0xe7, 0x91, 0x60, 0x21, 0xeb, 0x61, 0x5d, 0x23, 0x83, 0xe4, 0x33, 0xe9,
+    0xbc, 0x59, 0xb4, 0x7e, 0xf0, 0xae, 0xfb, 0x4d, 0xad, 0xb5, 0xde, 0x9e,
+    0x0c, 0x41, 0x00, 0x5b, 0xdc, 0xc0, 0x14, 0xb2, 0x18, 0x16, 0xf1, 0xfb,
+    0x8d, 0xe7, 0x67, 0x69, 0x71, 0xb3, 0x4e, 0xd3, 0x27, 0xfe, 0x7a, 0x4c,
+    0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa2, 0x68,
+    0x2f, 0x95, 0x1e, 0xdf, 0x6c, 0xba, 0xe7, 0x20, 0xc9, 0x74, 0xe2, 0x3a,
+    0xb5, 0xeb, 0x1a, 0x0d, 0xdf, 0xf9, 0x1a, 0xf0, 0x80, 0x41, 0x40, 0x28,
+    0x8f, 0xaf, 0x34, 0x58, 0xe4, 0xc5, 0x22, 0x58, 0x20, 0x32, 0x91, 0xcc,
+    0x36, 0xcb, 0xa9, 0xe7, 0xf6, 0x4b, 0xaf, 0xf9, 0xbc, 0x84, 0x1d, 0x1a,
+    0x66, 0xc8, 0x01, 0x1c, 0x05, 0x42, 0x31, 0x3a, 0x26, 0x3a, 0x5d, 0x2a,
+    0x12, 0xd6, 0x6d, 0x26, 0xf4,
+};
+
+static const uint8_t kCredentialID[] = {
+    0x91, 0x88, 0xEE, 0xF6, 0xE9, 0x75, 0xEF, 0x4E, 0x8B, 0x5B, 0x91,
+    0x34, 0xBF, 0x59, 0x89, 0x37, 0xE7, 0x91, 0x60, 0x21, 0xEB, 0x61,
+    0x5D, 0x23, 0x83, 0xE4, 0x33, 0xE9, 0xBC, 0x59, 0xB4, 0x7E, 0xF0,
+    0xAE, 0xFB, 0x4D, 0xAD, 0xB5, 0xDE, 0x9E, 0x0C, 0x41, 0x00, 0x5B,
+    0xDC, 0xC0, 0x14, 0xB2, 0x18, 0x16, 0xF1, 0xFB, 0x8D, 0xE7, 0x67,
+    0x69, 0x71, 0xB3, 0x4E, 0xD3, 0x27, 0xFE, 0x7A, 0x4C,
+};
+
+class iCloudKeychainTest : public testing::Test, FidoDiscoveryBase::Observer {
+ public:
+  void SetUp() override {
+    if (@available(macOS 13.3, *)) {
+      fake_ = base::MakeRefCounted<FakeSystemInterface>();
+      SetSystemInterfaceForTesting(fake_);
+      NSWindow* window = [[NSWindow alloc] init];
+      uintptr_t ns_window;
+      static_assert(sizeof(window) == sizeof(ns_window));
+      memcpy(&ns_window, (void*)&window, sizeof(ns_window));
+      discovery_ = NewDiscovery(ns_window);
+      discovery_->set_observer(this);
+      discovery_->Start();
+      task_environment_.RunUntilIdle();
+      CHECK(authenticator_);
+    }
+  }
+
+  void TearDown() override {
+    if (@available(macOS 13.3, *)) {
+      SetSystemInterfaceForTesting(nullptr);
+    }
+  }
+
+  // FidoDiscoveryBase::Observer:
+  void DiscoveryStarted(
+      FidoDiscoveryBase* discovery,
+      bool success,
+      std::vector<FidoAuthenticator*> authenticators) override {
+    CHECK(success);
+    CHECK_EQ(authenticators.size(), 1u);
+    CHECK(!authenticator_);
+    authenticator_ = authenticators[0];
+  }
+
+  void AuthenticatorAdded(FidoDiscoveryBase* discovery,
+                          FidoAuthenticator* authenticator) override {
+    NOTREACHED();
+  }
+
+  void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
+                            FidoAuthenticator* authenticator) override {
+    NOTREACHED();
+  }
+
+ protected:
+  API_AVAILABLE(macos(13.3))
+  scoped_refptr<FakeSystemInterface> fake_;
+  std::unique_ptr<FidoDiscoveryBase> discovery_;
+  FidoAuthenticator* authenticator_ = nullptr;
+  base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+TEST_F(iCloudKeychainTest, RequestAuthorization) {
+  if (@available(macOS 13.3, *)) {
+    PublicKeyCredentialParams public_key_params(
+        {PublicKeyCredentialParams::CredentialInfo()});
+    CtapMakeCredentialRequest make_credential_request(
+        "{}", {{1, 2, 3, 4}, "rp.id"}, {{4, 3, 2, 1}, "name", "displayName"},
+        std::move(public_key_params));
+    MakeCredentialOptions make_credential_options;
+
+    CtapGetAssertionRequest get_assertion_request("rp.id", "{}");
+    CtapGetAssertionOptions get_assertion_options;
+
+    for (const auto auth_state :
+         {SystemInterface::kAuthNotAuthorized, SystemInterface::kAuthDenied,
+          SystemInterface::kAuthAuthorized}) {
+      for (const bool is_make_credential : {true, false}) {
+        SCOPED_TRACE(auth_state);
+        SCOPED_TRACE(is_make_credential);
+
+        fake_->set_auth_state(auth_state);
+        if (auth_state == SystemInterface::kAuthNotAuthorized) {
+          fake_->set_next_auth_state(SystemInterface::kAuthAuthorized);
+        }
+
+        if (is_make_credential) {
+          test::TestCallbackReceiver<
+              CtapDeviceResponseCode,
+              absl::optional<AuthenticatorMakeCredentialResponse>>
+              callback;
+          authenticator_->MakeCredential(make_credential_request,
+                                         make_credential_options,
+                                         callback.callback());
+          callback.WaitForCallback();
+        } else {
+          test::TestCallbackReceiver<
+              CtapDeviceResponseCode,
+              std::vector<AuthenticatorGetAssertionResponse>>
+              callback;
+          authenticator_->GetAssertion(get_assertion_request,
+                                       get_assertion_options,
+                                       callback.callback());
+          callback.WaitForCallback();
+        }
+
+        // If the auth state was SystemInterface::kAuthNotAuthorized then
+        // authorisation should have been requested.
+        EXPECT_EQ(fake_->GetAuthState(),
+                  auth_state == SystemInterface::kAuthNotAuthorized
+                      ? SystemInterface::kAuthAuthorized
+                      : auth_state);
+      }
+    }
+  }
+}
+
+TEST_F(iCloudKeychainTest, MakeCredential) {
+  if (@available(macOS 13.3, *)) {
+    PublicKeyCredentialParams public_key_params(
+        {PublicKeyCredentialParams::CredentialInfo()});
+    CtapMakeCredentialRequest request("{}", {{1, 2, 3, 4}, "rp.id"},
+                                      {{4, 3, 2, 1}, "name", "displayName"},
+                                      std::move(public_key_params));
+    MakeCredentialOptions options;
+
+    auto make_credential = [this, &request, &options]()
+        -> std::tuple<CtapDeviceResponseCode,
+                      absl::optional<AuthenticatorMakeCredentialResponse>> {
+      test::TestCallbackReceiver<
+          CtapDeviceResponseCode,
+          absl::optional<AuthenticatorMakeCredentialResponse>>
+          callback;
+      authenticator_->MakeCredential(request, options, callback.callback());
+      callback.WaitForCallback();
+      return callback.TakeResult();
+    };
+
+    {
+      // Without `SetMakeCredentialResult` being called, an error is returned.
+      auto result = make_credential();
+      EXPECT_EQ(std::get<0>(result),
+                CtapDeviceResponseCode::kCtap2ErrOperationDenied);
+      EXPECT_FALSE(std::get<1>(result).has_value());
+    }
+
+    {
+      fake_->SetMakeCredentialError(8 /* exclude list match */);
+      auto result = make_credential();
+      EXPECT_EQ(std::get<0>(result),
+                CtapDeviceResponseCode::kCtap2ErrCredentialExcluded);
+      EXPECT_FALSE(std::get<1>(result).has_value());
+    }
+
+    {
+      static const uint8_t kWrongCredentialID[] = {1, 2, 3, 4};
+      fake_->SetMakeCredentialResult(kAttestationObjectBytes,
+                                     kWrongCredentialID);
+      auto result = make_credential();
+      EXPECT_EQ(std::get<0>(result), CtapDeviceResponseCode::kCtap2ErrOther);
+      ASSERT_FALSE(std::get<1>(result).has_value());
+    }
+
+    {
+      static const uint8_t kInvalidCBOR[] = {1, 2, 3, 4};
+      fake_->SetMakeCredentialResult(kInvalidCBOR, kCredentialID);
+      auto result = make_credential();
+      EXPECT_EQ(std::get<0>(result), CtapDeviceResponseCode::kCtap2ErrOther);
+      ASSERT_FALSE(std::get<1>(result).has_value());
+    }
+
+    {
+      // kInvalidAttestationStatement is an empty CBOR map, which is valid CBOR
+      // but invalid at a higher level.
+      static const uint8_t kInvalidAttestationStatement[] = {0xa0};
+      fake_->SetMakeCredentialResult(kInvalidAttestationStatement,
+                                     kCredentialID);
+      auto result = make_credential();
+      EXPECT_EQ(std::get<0>(result), CtapDeviceResponseCode::kCtap2ErrOther);
+      ASSERT_FALSE(std::get<1>(result).has_value());
+    }
+
+    {
+      fake_->SetMakeCredentialResult(kAttestationObjectBytes, kCredentialID);
+      auto result = make_credential();
+      EXPECT_EQ(std::get<0>(result), CtapDeviceResponseCode::kSuccess);
+      ASSERT_TRUE(std::get<1>(result).has_value());
+      const AuthenticatorMakeCredentialResponse response =
+          std::move(*std::get<1>(result));
+
+      const std::vector<uint8_t> returned_credential_id =
+          response.attestation_object.authenticator_data().GetCredentialId();
+      EXPECT_TRUE(base::ranges::equal(returned_credential_id, kCredentialID));
+      EXPECT_FALSE(response.enterprise_attestation_returned);
+      EXPECT_TRUE(response.is_resident_key.value_or(false));
+      EXPECT_FALSE(response.enterprise_attestation_returned);
+      EXPECT_EQ(response.transports->size(), 2u);
+      EXPECT_TRUE(
+          base::Contains(*response.transports, FidoTransportProtocol::kHybrid));
+      EXPECT_TRUE(base::Contains(*response.transports,
+                                 FidoTransportProtocol::kInternal));
+      EXPECT_EQ(response.transport_used, FidoTransportProtocol::kInternal);
+    }
+  }
+}
+
+TEST_F(iCloudKeychainTest, GetAssertion) {
+  static const uint8_t kAuthenticatorData[] = {
+      0x26, 0xbd, 0x72, 0x78, 0xbe, 0x46, 0x37, 0x61, 0xf1, 0xfa,
+      0xa1, 0xb1, 0x0a, 0xb4, 0xc4, 0xf8, 0x26, 0x70, 0x26, 0x9c,
+      0x41, 0x0c, 0x72, 0x6a, 0x1f, 0xd6, 0xe0, 0x58, 0x55, 0xe1,
+      0x9b, 0x46, 0x01, 0x00, 0x00, 0x0f, 0xdd,
+  };
+  static const uint8_t kSignature[] = {1, 2, 3, 4};
+  static const uint8_t kUserID[] = {5, 6, 7, 8};
+
+  if (@available(macOS 13.3, *)) {
+    CtapGetAssertionRequest request("rp.id", "{}");
+    CtapGetAssertionOptions options;
+
+    auto get_assertion = [this, &request, &options]()
+        -> std::tuple<CtapDeviceResponseCode,
+                      std::vector<AuthenticatorGetAssertionResponse>> {
+      test::TestCallbackReceiver<CtapDeviceResponseCode,
+                                 std::vector<AuthenticatorGetAssertionResponse>>
+          callback;
+      authenticator_->GetAssertion(request, options, callback.callback());
+      callback.WaitForCallback();
+      return callback.TakeResult();
+    };
+
+    {
+      // Without `SetGetAssertionResult` being called, an error is returned.
+      auto result = get_assertion();
+      EXPECT_EQ(std::get<0>(result),
+                CtapDeviceResponseCode::kCtap2ErrOperationDenied);
+      EXPECT_TRUE(std::get<1>(result).empty());
+    }
+
+    {
+      static const uint8_t kInvalidAuthenticatorData[] = {1, 2, 3, 4};
+      fake_->SetGetAssertionResult(kInvalidAuthenticatorData, kSignature,
+                                   kUserID, kCredentialID);
+      auto result = get_assertion();
+      EXPECT_EQ(std::get<0>(result), CtapDeviceResponseCode::kCtap2ErrOther);
+      EXPECT_TRUE(std::get<1>(result).empty());
+    }
+
+    {
+      fake_->SetGetAssertionResult(kAuthenticatorData, kSignature, kUserID,
+                                   kCredentialID);
+      auto result = get_assertion();
+      EXPECT_EQ(std::get<0>(result), CtapDeviceResponseCode::kSuccess);
+      EXPECT_EQ(std::get<1>(result).size(), 1u);
+
+      AuthenticatorGetAssertionResponse response =
+          std::move(std::get<1>(result)[0]);
+      EXPECT_TRUE(base::ranges::equal(response.signature, kSignature));
+      EXPECT_TRUE(base::ranges::equal(response.user_entity->id, kUserID));
+      EXPECT_TRUE(base::ranges::equal(response.credential->id, kCredentialID));
+      EXPECT_TRUE(response.user_selected);
+      EXPECT_EQ(response.transport_used, FidoTransportProtocol::kInternal);
+    }
+  }
+}
+
+TEST_F(iCloudKeychainTest, FetchCredentialMetadata) {
+  if (@available(macOS 13.3, *)) {
+    const std::vector<DiscoverableCredentialMetadata> creds = {
+        {AuthenticatorType::kICloudKeychain,
+         "example.com",
+         {1, 2, 3, 4},
+         {{4, 3, 2, 1}, "name", absl::nullopt}}};
+    fake_->SetCredentials(creds);
+    test::TestCallbackReceiver<std::vector<DiscoverableCredentialMetadata>,
+                               FidoRequestHandlerBase::RecognizedCredential>
+        callback;
+    CtapGetAssertionRequest request("example.com", "{}");
+    CtapGetAssertionOptions options;
+
+    CHECK(authenticator_);
+    authenticator_->GetPlatformCredentialInfoForRequest(request, options,
+                                                        callback.callback());
+    callback.WaitForCallback();
+    auto result = callback.TakeResult();
+    std::vector<DiscoverableCredentialMetadata> creds_out =
+        std::move(std::get<0>(result));
+
+    ASSERT_EQ(creds_out.size(), 1u);
+    EXPECT_EQ(creds[0], creds_out[0]);
+  }
+}
+
+}  // namespace
+
+}  // namespace device::fido::icloud_keychain
diff --git a/device/fido/mac/touch_id_context.mm b/device/fido/mac/touch_id_context.mm
index 6655a87..90f1c56 100644
--- a/device/fido/mac/touch_id_context.mm
+++ b/device/fido/mac/touch_id_context.mm
@@ -8,9 +8,9 @@
 #import <Foundation/Foundation.h>
 #include <Security/Security.h>
 
+#include "base/apple/bridging.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/memory/ptr_util.h"
@@ -50,7 +50,7 @@
     return false;
   }
 
-  NSArray* entitlement_value_nsarray = base::mac::CFToNSPtrCast(
+  NSArray* entitlement_value_nsarray = base::apple::CFToNSPtrCast(
       base::mac::CFCast<CFArrayRef>(entitlement_value_cftype));
   if (!entitlement_value_nsarray) {
     return false;
@@ -73,7 +73,7 @@
   CFDictionarySetValue(params, kSecAttrKeyType,
                        kSecAttrKeyTypeECSECPrimeRandom);
   CFDictionarySetValue(params, kSecAttrKeySizeInBits,
-                       base::mac::NSToCFPtrCast(@256));
+                       base::apple::NSToCFPtrCast(@256));
   CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
   CFDictionarySetValue(params, kSecAttrIsPermanent, kCFBooleanFalse);
 
@@ -195,7 +195,7 @@
                                 // were, it would have to be retained first.
                                 DCHECK(error != nil);
                                 DVLOG(1) << "Touch ID prompt failed: "
-                                         << base::mac::NSToCFPtrCast(error);
+                                         << base::apple::NSToCFPtrCast(error);
                               }
                               runner->PostTask(
                                   FROM_HERE,
diff --git a/device/gamepad/gamepad_device_mac.mm b/device/gamepad/gamepad_device_mac.mm
index 648d062..cf637253 100644
--- a/device/gamepad/gamepad_device_mac.mm
+++ b/device/gamepad/gamepad_device_mac.mm
@@ -7,7 +7,7 @@
 #include <CoreFoundation/CoreFoundation.h>
 #import <Foundation/Foundation.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/strings/sys_string_conversions.h"
diff --git a/device/gamepad/gamepad_platform_data_fetcher_mac.mm b/device/gamepad/gamepad_platform_data_fetcher_mac.mm
index e0188ce..eabebf2 100644
--- a/device/gamepad/gamepad_platform_data_fetcher_mac.mm
+++ b/device/gamepad/gamepad_platform_data_fetcher_mac.mm
@@ -7,7 +7,7 @@
 #include <stdint.h>
 #include <string.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/task/sequenced_task_runner.h"
@@ -65,7 +65,7 @@
     DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber),
   ];
   IOHIDManagerSetDeviceMatchingMultiple(hid_manager_ref_,
-                                        base::mac::NSToCFPtrCast(criteria));
+                                        base::apple::NSToCFPtrCast(criteria));
 
   RegisterForNotifications();
 }
@@ -143,8 +143,8 @@
 }
 
 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
+  using base::apple::CFToNSPtrCast;
   using base::mac::CFCastStrict;
-  using base::mac::CFToNSPtrCast;
 
   if (!enabled_) {
     return;
diff --git a/docs/configuration.md b/docs/configuration.md
index 275ec59..a813217 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -137,6 +137,16 @@
 decent litmus test for whether something should be a flag or a setting is: "will
 someone who can't read or write code want to change this?"
 
+## Summary Table
+|                                              | Prefs       | Features       | Switches | Flags                               | Settings                          |
+| :-                                           | :-          | :-             | :--:     | :--:                                | :-                                |
+| Directly surfaced to the user                | ❌          | ❌            | ❌       | ✅                                  | ✅                                |
+| Localized into the user's language           | ❌          | ❌            | ❌       | ❌                                  | ✅                                |
+| Configurable via enterprise policy           | ✅          | ❌            | ✅       | ✅                                  | ❌ but their backing prefs may be |
+| Reported when in use                         | ❌          | via UMA/crash |  ❌      | via UMA<br> `Launch.FlagsAtStartup` | ❌                                |
+| Included in chrome://version                 | ❌          | ✅            | ✅       | ❌                                  | ❌                                |
+| Automatically persistent<br> across restarts | ✅ usually  | ❌            | ❌       | ✅                                  | ✅ via backing prefs              |
+
 ## Related Documents
 
 * [Chromium Feature API & Finch (Googler-only)](http://go/finch-feature-api)
diff --git a/docs/mac/arc.md b/docs/mac/arc.md
index 2790f320..ac436c98 100644
--- a/docs/mac/arc.md
+++ b/docs/mac/arc.md
@@ -341,7 +341,7 @@
   ownership management as Objective-C object pointers.
 - `CFToNSCast` and `NSToCFCast`: These do not handle ARC ownership; switch to
   `CFToNSPtrCast`, `CFToNSOwnershipCast`, `NSToCFPtrCast`, and
-  `NSToCFOwnershipCast` from `base/mac/bridging.h`.
+  `NSToCFOwnershipCast` from `base/apple/bridging.h`.
 
 ## Further reading {#references}
 
diff --git a/docs/updater/functional_spec.md b/docs/updater/functional_spec.md
index 5adf610..fa9cac8 100644
--- a/docs/updater/functional_spec.md
+++ b/docs/updater/functional_spec.md
@@ -865,7 +865,9 @@
 they've been created. If a task execution is missed, it will run as soon as the
 system is able to.
 
-The updater also runs at user login.
+The updater also runs at user login. For system installs, this is done via a
+logon trigger on the scheduled task. For user installs, this is done via the
+"Run" registry entry in `HKCU`.
 
 ### On-Demand Updates
 The updater exposes an RPC interface for any user to trigger an update check.
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index 6096ff5..7423353 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -603,6 +603,16 @@
     DCHECK(extension);
   }
 
+  // `ValidateSourceContextAndExtractExtensionId` is called before coming here.
+  // Therefore, possible origins are either an extension origin or an opaque
+  // origin created by an extension. See https://crbug.com/1407087.
+  url::Origin source_origin = url::Origin();
+  if (source.is_for_render_frame()) {
+    source_origin = source.GetRenderFrameHost()->GetLastCommittedOrigin();
+  } else if (source.is_for_service_worker() && extension) {
+    source_origin = extension->origin();
+  }
+
   BrowserContext* receiver_context = receiver_contents->GetBrowserContext();
   DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(receiver_context,
                                                        context_));
@@ -613,9 +623,8 @@
           ExtensionApiFrameIdMap::FrameData(), receiver.release(),
           receiver_port_id, MessagingEndpoint::ForExtension(extension_id),
           std::move(opener_port), extension_id,
-          GURL(),         // Source URL doesn't make sense for opening to tabs.
-          url::Origin(),  // Origin URL doesn't make sense for opening to tabs.
-          channel_type, channel_name,
+          GURL(),  // Source URL doesn't make sense for opening to tabs.
+          source_origin, channel_type, channel_name,
           false);  // Connections to tabs aren't webview guests.
   OpenChannelImpl(receiver_context, std::move(params), extension,
                   false /* did_enqueue */);
diff --git a/gin/gin_features.cc b/gin/gin_features.cc
index 96714e6..fef95f01 100644
--- a/gin/gin_features.cc
+++ b/gin/gin_features.cc
@@ -152,6 +152,12 @@
              "V8UseLibmTrigFunctions",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Elide redundant TDZ hole checks in bytecode. This only sets the V8 flag when
+// manually overridden.
+BASE_FEATURE(kV8IgnitionElideRedundantTdzChecks,
+             "V8IgnitionElideRedundantTdzChecks",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // JavaScript language features.
 
 // Enables the Symbols-as-WeakMap-keys proposal.
diff --git a/gin/gin_features.h b/gin/gin_features.h
index 7399e408..e0cc3c4f 100644
--- a/gin/gin_features.h
+++ b/gin/gin_features.h
@@ -25,6 +25,7 @@
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8FlushBaselineCode);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8FlushBytecode);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8FlushEmbeddedBlobICache);
+GIN_EXPORT BASE_DECLARE_FEATURE(kV8IgnitionElideRedundantTdzChecks);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8LazyFeedbackAllocation);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8Maglev);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8MinorMC);
diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
index ee54275..0ea2663 100644
--- a/gin/v8_initializer.cc
+++ b/gin/v8_initializer.cc
@@ -333,6 +333,10 @@
                            "--no-slow-histograms");
   }
 
+  SetV8FlagsIfOverridden(features::kV8IgnitionElideRedundantTdzChecks,
+                         "--ignition-elide-redundant-tdz-checks",
+                         "--no-ignition-elide-redundant-tdz-checks");
+
   // JavaScript language features.
   SetV8FlagsIfOverridden(features::kJavaScriptSymbolAsWeakMapKey,
                          "--harmony-symbol-as-weakmap-key",
diff --git a/google_apis/gaia/gaia_urls.cc b/google_apis/gaia/gaia_urls.cc
index 690b112..7b36d99 100644
--- a/google_apis/gaia/gaia_urls.cc
+++ b/google_apis/gaia/gaia_urls.cc
@@ -90,6 +90,8 @@
 const char kAccountCapabilitiesBatchGetUrlSuffix[] =
     "v1/accountcapabilities:batchGet";
 
+const char kRotateBoundCookiesUrlSuffix[] = "RotateBoundCookies";
+
 GaiaUrls* g_instance_for_testing = nullptr;
 
 void GetSwitchValueWithDefault(base::StringPiece switch_value,
@@ -293,6 +295,10 @@
   return reauth_api_url_;
 }
 
+const GURL& GaiaUrls::rotate_bound_cookies_url() const {
+  return rotate_bound_cookies_url_;
+}
+
 const GURL& GaiaUrls::classroom_api_origin_url() const {
   return classroom_api_origin_url_;
 }
@@ -396,6 +402,8 @@
   ResolveURLIfInvalid(&reauth_url_, gaia_url, kReauthSuffix);
   ResolveURLIfInvalid(&get_check_connection_info_url_, gaia_url,
                       kGetCheckConnectionInfoSuffix);
+  ResolveURLIfInvalid(&rotate_bound_cookies_url_, gaia_url,
+                      kRotateBoundCookiesUrlSuffix);
 
   // URLs from |lso_origin_url_|.
   ResolveURLIfInvalid(&oauth2_revoke_url_, lso_origin_url_,
@@ -469,4 +477,5 @@
   config->GetURLIfExists(URL_KEY_AND_PTR(oauth2_token_info_url));
   config->GetURLIfExists(URL_KEY_AND_PTR(oauth2_revoke_url));
   config->GetURLIfExists(URL_KEY_AND_PTR(reauth_api_url));
+  config->GetURLIfExists(URL_KEY_AND_PTR(rotate_bound_cookies_url));
 }
diff --git a/google_apis/gaia/gaia_urls.h b/google_apis/gaia/gaia_urls.h
index 1c67c7ef..2f71d1e 100644
--- a/google_apis/gaia/gaia_urls.h
+++ b/google_apis/gaia/gaia_urls.h
@@ -60,7 +60,7 @@
   const GURL& oauth2_token_info_url() const;
   const GURL& oauth2_revoke_url() const;
   const GURL& reauth_api_url() const;
-
+  const GURL& rotate_bound_cookies_url() const;
   const GURL& classroom_api_origin_url() const;
   const GURL& tasks_api_origin_url() const;
 
@@ -118,6 +118,7 @@
   GURL oauth2_revoke_url_;
 
   GURL reauth_api_url_;
+  GURL rotate_bound_cookies_url_;
 };
 
 #endif  // GOOGLE_APIS_GAIA_GAIA_URLS_H_
diff --git a/gpu/command_buffer/service/copy_shared_image_helper.cc b/gpu/command_buffer/service/copy_shared_image_helper.cc
index 1f812be3..6c71c4f 100644
--- a/gpu/command_buffer/service/copy_shared_image_helper.cc
+++ b/gpu/command_buffer/service/copy_shared_image_helper.cc
@@ -30,6 +30,9 @@
 #include "third_party/skia/include/gpu/GrDirectContext.h"
 #include "third_party/skia/include/gpu/GrYUVABackendTextures.h"
 #include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"
+#include "third_party/skia/include/gpu/graphite/Context.h"
+#include "third_party/skia/include/gpu/graphite/Recorder.h"
+#include "third_party/skia/include/gpu/graphite/YUVABackendTextures.h"
 
 namespace gpu {
 
@@ -159,6 +162,20 @@
   return base::ok();
 }
 
+void InsertRecordingAndSubmit(SharedContextState* context,
+                              bool sync_cpu = false) {
+  CHECK(context->graphite_context());
+  auto recording = context->gpu_main_graphite_recorder()->snap();
+  if (recording) {
+    skgpu::graphite::InsertRecordingInfo info = {};
+    info.fRecording = recording.get();
+    context->graphite_context()->insertRecording(info);
+  }
+  context->graphite_context()->submit(sync_cpu
+                                          ? skgpu::graphite::SyncToCpu::kYes
+                                          : skgpu::graphite::SyncToCpu::kNo);
+}
+
 void FlushSurface(SkiaImageRepresentation::ScopedWriteAccess* access) {
   int num_planes = access->representation()->format().NumberOfPlanes();
   for (int plane_index = 0; plane_index < num_planes; plane_index++) {
@@ -177,6 +194,7 @@
   // This will ensure that vulkan memory allocated on gpu main thread will be
   // cleaned up.
   if (!signal_semaphores.empty() || is_drdc_enabled) {
+    CHECK(context->gr_context());
     GrFlushInfo flush_info = {
         .fNumSemaphores = signal_semaphores.size(),
         .fSignalSemaphores = signal_semaphores.data(),
@@ -202,8 +220,13 @@
       sync_cpu || !signal_semaphores.empty() || is_drdc_enabled;
 
   if (need_submit) {
+    CHECK(context->gr_context());
     context->gr_context()->submit(sync_cpu);
   }
+
+  if (context->graphite_context()) {
+    InsertRecordingAndSubmit(context, sync_cpu);
+  }
 }
 
 sk_sp<SkColorSpace> ReadSkColorSpace(const volatile GLbyte* bytes) {
@@ -452,12 +475,6 @@
 
   bool drew_image = false;
   if (source_access_valid) {
-    std::array<GrBackendTexture, SkYUVAInfo::kMaxPlanes> yuva_textures;
-    for (int i = 0; i < num_src_planes; ++i) {
-      yuva_textures[i] =
-          source_scoped_access[i]->promise_image_texture()->backendTexture();
-    }
-
     // Disable color space conversion if no source color space was specified.
     if (!src_rgb_color_space) {
       if (auto dest_color_space = dest_surface->imageInfo().refColorSpace()) {
@@ -465,15 +482,35 @@
       }
     }
 
+    sk_sp<SkImage> result_image;
     SkISize dest_size =
         SkISize::Make(dest_surface->width(), dest_surface->height());
     SkYUVAInfo yuva_info(dest_size, src_plane_config, src_subsampling,
                          src_yuv_color_space);
-    GrYUVABackendTextures yuva_backend_textures(yuva_info, yuva_textures.data(),
-                                                kTopLeft_GrSurfaceOrigin);
-    auto result_image = SkImages::TextureFromYUVATextures(
-        shared_context_state_->gr_context(), yuva_backend_textures,
-        src_rgb_color_space);
+    if (auto* gr_context = shared_context_state_->gr_context()) {
+      std::array<GrBackendTexture, SkYUVAInfo::kMaxPlanes> yuva_textures;
+      for (int i = 0; i < num_src_planes; ++i) {
+        yuva_textures[i] =
+            source_scoped_access[i]->promise_image_texture()->backendTexture();
+      }
+      GrYUVABackendTextures yuva_backend_textures(
+          yuva_info, yuva_textures.data(), kTopLeft_GrSurfaceOrigin);
+      result_image = SkImages::TextureFromYUVATextures(
+          gr_context, yuva_backend_textures, src_rgb_color_space);
+    } else {
+      CHECK(shared_context_state_->graphite_context());
+      auto* recorder = shared_context_state_->gpu_main_graphite_recorder();
+      std::array<skgpu::graphite::BackendTexture, SkYUVAInfo::kMaxPlanes>
+          yuva_textures;
+      for (int i = 0; i < num_src_planes; ++i) {
+        yuva_textures[i] = source_scoped_access[i]->graphite_texture();
+      }
+      skgpu::graphite::YUVABackendTextures yuva_backend_textures(
+          recorder, yuva_info, yuva_textures.data());
+      result_image = SkImage::MakeGraphiteFromYUVABackendTextures(
+          recorder, yuva_backend_textures, src_rgb_color_space);
+    }
+
     if (!result_image) {
       result = base::unexpected(
           GLError(GL_INVALID_OPERATION, "glConvertYUVAMailboxesToRGB",
@@ -706,6 +743,7 @@
     GLsizei height,
     GLboolean flip_y,
     const volatile GLbyte* src_mailbox) {
+  CHECK(shared_context_state_->gr_context());
   Mailbox source_mailbox = Mailbox::FromVolatile(
       reinterpret_cast<const volatile Mailbox*>(src_mailbox)[0]);
   DLOG_IF(ERROR, !source_mailbox.Verify())
diff --git a/gpu/command_buffer/service/copy_shared_image_helper.h b/gpu/command_buffer/service/copy_shared_image_helper.h
index afdd1ae..206593c 100644
--- a/gpu/command_buffer/service/copy_shared_image_helper.h
+++ b/gpu/command_buffer/service/copy_shared_image_helper.h
@@ -57,6 +57,7 @@
       GLboolean unpack_flip_y,
       const volatile GLbyte* mailboxes);
   // Only used by passthrough decoder.
+  // TODO(crbug.com/1444777): Handle this use-case for graphite.
   base::expected<void, GLError> CopySharedImageToGLTexture(
       GLuint texture_service_id,
       GLenum target,
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index d190e8c..c255e984 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -78,6 +78,7 @@
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
 #include "third_party/skia/include/gpu/GrYUVABackendTextures.h"
+#include "third_party/skia/include/gpu/graphite/Context.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/geometry/skia_conversions.h"
@@ -291,14 +292,32 @@
     AddToPendingQueue(submit_count);
     finished_ = false;
 
-    auto* gr_context = shared_context_state_->gr_context();
-    GrFlushInfo info;
-    info.fFinishedProc = RasterCommandsCompletedQuery::FinishedProc;
-    auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
-    info.fFinishedContext =
-        new base::WeakPtr<RasterCommandsCompletedQuery>(weak_ptr);
-    gr_context->flush(info);
-    gr_context->submit();
+    if (auto* gr_context = shared_context_state_->gr_context()) {
+      GrFlushInfo info;
+      info.fFinishedProc = RasterCommandsCompletedQuery::FinishedProc;
+      auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
+      info.fFinishedContext =
+          new base::WeakPtr<RasterCommandsCompletedQuery>(weak_ptr);
+      gr_context->flush(info);
+      gr_context->submit();
+    } else {
+      CHECK(shared_context_state_->graphite_context());
+      auto recording =
+          shared_context_state_->gpu_main_graphite_recorder()->snap();
+      if (recording) {
+        skgpu::graphite::InsertRecordingInfo info = {};
+        info.fRecording = recording.get();
+        info.fFinishedProc = [](void* context, skgpu::CallbackResult result) {
+          RasterCommandsCompletedQuery::FinishedProc(context);
+        };
+        info.fFinishedContext = new base::WeakPtr<RasterCommandsCompletedQuery>(
+            weak_ptr_factory_.GetWeakPtr());
+        shared_context_state_->graphite_context()->insertRecording(info);
+        shared_context_state_->graphite_context()->submit();
+      } else {
+        finished_ = true;
+      }
+    }
   }
 
   void QueryCounter(base::subtle::Atomic32 submit_count) override {
@@ -353,7 +372,8 @@
                      scoped_refptr<gpu::Buffer> buffer,
                      QuerySync* sync) override {
     if (target == GL_COMMANDS_COMPLETED_CHROMIUM &&
-        shared_context_state_->gr_context()) {
+        (shared_context_state_->gr_context() ||
+         shared_context_state_->graphite_context())) {
       auto query = base::MakeRefCounted<RasterCommandsCompletedQuery>(
           shared_context_state_, this, target, std::move(buffer), sync);
       std::pair<QueryMap::iterator, bool> result =
@@ -559,6 +579,9 @@
   GrDirectContext* gr_context() const {
     return shared_context_state_->gr_context();
   }
+  skgpu::graphite::Context* graphite_context() const {
+    return shared_context_state_->graphite_context();
+  }
   skgpu::graphite::Recorder* graphite_recorder() const {
     return shared_context_state_->gpu_main_graphite_recorder();
   }
@@ -582,8 +605,11 @@
     // The workaround is not needed for arm based macs (because they don't have
     // the bug).
 #if BUILDFLAG(IS_MAC) && !defined(ARCH_CPU_ARM64)
-    if (!shared_context_state_->GrContextIsGL())
+    // The workaround is also not needed for Graphite, which always uses Metal
+    // drivers (via Dawn).
+    if (!shared_context_state_->GrContextIsGL()) {
       return;
+    }
     // This function does aggressive flushes to work around crashes in the
     // macOS OpenGL driver.
     // https://crbug.com/906453
@@ -713,6 +739,24 @@
       const volatile GLuint* paint_cache_ids);
   void DoClearPaintCacheINTERNAL();
 
+  void GraphiteFlushAndSubmitWithRecording(
+      std::unique_ptr<skgpu::graphite::Recording> recording,
+      skgpu::graphite::SyncToCpu sync_to_cpu =
+          skgpu::graphite::SyncToCpu::kNo) {
+    if (recording) {
+      skgpu::graphite::InsertRecordingInfo info = {};
+      info.fRecording = recording.get();
+      graphite_context()->insertRecording(info);
+    }
+    graphite_context()->submit(sync_to_cpu);
+  }
+
+  void GraphiteFlushAndSubmit(skgpu::graphite::SyncToCpu sync_to_cpu =
+                                  skgpu::graphite::SyncToCpu::kNo) {
+    GraphiteFlushAndSubmitWithRecording(graphite_recorder()->snap(),
+                                        sync_to_cpu);
+  }
+
   void FlushSurface(SkiaImageRepresentation::ScopedWriteAccess* access) {
     static int flush_count = 0;
     const base::TimeTicks start = base::TimeTicks::Now();
@@ -723,6 +767,11 @@
       surface->flush();
     }
     access->ApplyBackendSurfaceEndState();
+
+    if (graphite_context()) {
+      GraphiteFlushAndSubmit();
+    }
+
     if (flush_count < 100) {
       ++flush_count;
       base::UmaHistogramCustomMicrosecondsTimes(
@@ -737,6 +786,9 @@
     // This will ensure that vulkan memory allocated on gpu main thread will be
     // cleaned up.
     if (!signal_semaphores.empty() || is_drdc_enabled_) {
+      // NOTE: The Graphite SharedImage representation does not set semaphores,
+      // and we are not enabling DrDC with Graphite.
+      CHECK(gr_context());
       GrFlushInfo flush_info = {
           .fNumSemaphores = signal_semaphores.size(),
           .fSignalSemaphores = signal_semaphores.data(),
@@ -761,8 +813,13 @@
     const bool need_submit =
         sync_cpu || !signal_semaphores.empty() || is_drdc_enabled_;
 
-    if (need_submit)
+    if (need_submit) {
+      // NOTE: Graphite uses Metal (via Dawn), the Graphite SharedImage
+      // representation does not set semaphores, and we are not enabling DrDC
+      // with Graphite.
+      CHECK(gr_context());
       gr_context()->submit(sync_cpu);
+    }
   }
 
 #if defined(NDEBUG)
@@ -1062,7 +1119,7 @@
       return ContextResult::kFatalFailure;
     }
 
-    DCHECK(gr_context());
+    DCHECK(gr_context() || graphite_context());
     use_gpu_raster_ = true;
     paint_cache_ = std::make_unique<cc::ServicePaintCache>();
   }
@@ -1201,6 +1258,10 @@
         gr_context()->colorTypeSupportedAsImage(kA16_unorm_SkColorType);
     caps.texture_half_float_linear =
         gr_context()->colorTypeSupportedAsImage(kA16_float_SkColorType);
+  } else if (graphite_context()) {
+    // TODO(b/281151641): Determine if there are checks to be made here.
+    caps.texture_norm16 = true;
+    caps.texture_half_float_linear = true;
   } else {
     caps.texture_norm16 = feature_info()->feature_flags().ext_texture_norm16;
     caps.texture_half_float_linear =
@@ -1316,8 +1377,11 @@
 
 void RasterDecoderImpl::ProcessPendingQueries(bool did_finish) {
   if (query_manager_) {
-    if (auto* gr_context = shared_context_state_->gr_context())
-      gr_context->checkAsyncWorkCompletion();
+    if (gr_context()) {
+      gr_context()->checkAsyncWorkCompletion();
+    } else if (graphite_context()) {
+      graphite_context()->checkAsyncWorkCompletion();
+    }
     query_manager_->ProcessPendingQueries(did_finish);
   }
 }
@@ -1804,15 +1868,19 @@
 }
 
 void RasterDecoderImpl::DoFinish() {
-  if (auto* gr_context = shared_context_state_->gr_context()) {
-    gr_context->flushAndSubmit(/*syncCpu=*/true);
+  if (gr_context()) {
+    gr_context()->flushAndSubmit(/*syncCpu=*/true);
+  } else if (graphite_context()) {
+    GraphiteFlushAndSubmit(skgpu::graphite::SyncToCpu::kYes);
   }
   ProcessPendingQueries(/*did_finish=*/true);
 }
 
 void RasterDecoderImpl::DoFlush() {
-  if (auto* gr_context = shared_context_state_->gr_context()) {
-    gr_context->flushAndSubmit(/*syncCpu=*/false);
+  if (gr_context()) {
+    gr_context()->flushAndSubmit(/*syncCpu=*/false);
+  } else if (graphite_context()) {
+    GraphiteFlushAndSubmit();
   }
   ProcessPendingQueries(/*did_finish=*/false);
 }
@@ -2026,7 +2094,10 @@
   }
 
   // Try a direct texture upload without using SkSurface.
-  if (gfx::Size(src_width, src_height) == dest_shared_image->size() &&
+  // TODO(crbug.com/1423576): Enable this path for Graphite after fixing
+  // RGBA/BGRA mismatch.
+  if (!graphite_context() &&
+      gfx::Size(src_width, src_height) == dest_shared_image->size() &&
       x_offset == 0 && y_offset == 0 &&
       (src_info.alphaType() == dest_shared_image->alpha_type() ||
        src_info.alphaType() == kUnknown_SkAlphaType) &&
@@ -2104,19 +2175,35 @@
     return false;
   }
   if (!begin_semaphores.empty()) {
-    bool result = shared_context_state_->gr_context()->wait(
-        begin_semaphores.size(), begin_semaphores.data(),
-        /*deleteSemaphoresAfterWait=*/false);
+    // The Graphite SharedImage representation does not set semaphores.
+    CHECK(gr_context());
+    bool result =
+        gr_context()->wait(begin_semaphores.size(), begin_semaphores.data(),
+                           /*deleteSemaphoresAfterWait=*/false);
     DCHECK(result);
   }
 
   SkPixmap pixmap(src_info, pixel_data, row_bytes);
-  bool written = gr_context()->updateBackendTexture(
-      dest_scoped_access->promise_image_texture(plane_index)->backendTexture(),
-      &pixmap,
-      /*levels=*/1, dest_shared_image->surface_origin(), nullptr, nullptr);
-
-  dest_scoped_access->ApplyBackendSurfaceEndState();
+  bool written = false;
+  if (gr_context()) {
+    written = gr_context()->updateBackendTexture(
+        dest_scoped_access->promise_image_texture(plane_index)
+            ->backendTexture(),
+        &pixmap,
+        /*numLevels=*/1, dest_shared_image->surface_origin(), nullptr, nullptr);
+    dest_scoped_access->ApplyBackendSurfaceEndState();
+  } else {
+    CHECK(graphite_context());
+    written = graphite_recorder()->updateBackendTexture(
+        dest_scoped_access->graphite_texture(plane_index), &pixmap,
+        /*numLevels=*/1);
+    auto recording = graphite_recorder()->snap();
+    if (!recording) {
+      DLOG(ERROR) << "Failed to snap Graphite recording";
+      return false;
+    }
+    GraphiteFlushAndSubmitWithRecording(std::move(recording));
+  }
 
   SubmitIfNecessary(std::move(end_semaphores));
   if (written && !dest_shared_image->IsCleared()) {
@@ -2654,11 +2741,20 @@
       flags = 0;
       break;
     case kMSAA:
+      // Graphite operates as in the kDMSAA case below.
+      if (graphite_context()) {
+        final_msaa_count = 1;
+        flags = SkSurfaceProps::kDynamicMSAA_Flag;
+        break;
+      }
+
       // If we can't match requested MSAA samples, don't use MSAA.
       final_msaa_count = std::max(static_cast<int>(msaa_sample_count), 0);
-      if (final_msaa_count >
-          gr_context()->maxSurfaceSampleCountForColorType(sk_color_type))
+      if (gr_context() &&
+          final_msaa_count >
+              gr_context()->maxSurfaceSampleCountForColorType(sk_color_type)) {
         final_msaa_count = 0;
+      }
       flags = 0;
       break;
     case kDMSAA:
diff --git "a/infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT \050ATS\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT \050ATS\051/properties.json"
deleted file mode 100644
index 8a8c964..0000000
--- "a/infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT \050ATS\051/properties.json"
+++ /dev/null
@@ -1,61 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Android ARM 32-bit Goma RBE ToT (ATS)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_android_config": {
-                "config": "main_builder_mb"
-              },
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "goma_failfast",
-                  "goma_client_candidate",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 32,
-                "target_platform": "android"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "android"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Android ARM 32-bit Goma RBE ToT (ATS)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT/properties.json b/infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT/properties.json
deleted file mode 100644
index 1775675..0000000
--- a/infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT/properties.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Android ARM 32-bit Goma RBE ToT",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_android_config": {
-                "config": "main_builder_mb"
-              },
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "goma_failfast",
-                  "goma_client_candidate",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 32,
-                "target_platform": "android"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "android"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Android ARM 32-bit Goma RBE ToT",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging \050clobber\051/properties.json"
deleted file mode 100644
index 9794a382..0000000
--- "a/infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging \050clobber\051/properties.json"
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Linux Goma RBE Staging (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Linux Goma RBE Staging (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?staging",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging \050dbg\051 \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging \050dbg\051 \050clobber\051/properties.json"
deleted file mode 100644
index 8a0cb36..0000000
--- "a/infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging \050dbg\051 \050clobber\051/properties.json"
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Linux Goma RBE Staging (dbg) (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Linux Goma RBE Staging (dbg) (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?staging",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT \050ATS\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT \050ATS\051/properties.json"
deleted file mode 100644
index e0ee79b..0000000
--- "a/infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT \050ATS\051/properties.json"
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Linux Goma RBE ToT (ATS)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "goma_client_candidate",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Linux Goma RBE ToT (ATS)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT/properties.json b/infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT/properties.json
deleted file mode 100644
index cb37f7db..0000000
--- a/infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT/properties.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Linux Goma RBE ToT",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "goma_client_candidate",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Linux Goma RBE ToT",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Chromium Mac Goma RBE Staging \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Mac Goma RBE Staging \050clobber\051/properties.json"
deleted file mode 100644
index 5edad2b..0000000
--- "a/infra/config/generated/builders/goma/Chromium Mac Goma RBE Staging \050clobber\051/properties.json"
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Mac Goma RBE Staging (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Mac Goma RBE Staging (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "jobs": 80,
-    "rpc_extra_params": "?staging",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Chromium Mac Goma RBE ToT/properties.json b/infra/config/generated/builders/goma/Chromium Mac Goma RBE ToT/properties.json
deleted file mode 100644
index 867967d..0000000
--- a/infra/config/generated/builders/goma/Chromium Mac Goma RBE ToT/properties.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Mac Goma RBE ToT",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "goma_client_candidate"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Mac Goma RBE ToT",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "jobs": 80,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Chromium Win Goma RBE ATS Staging \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Win Goma RBE ATS Staging \050clobber\051/properties.json"
deleted file mode 100644
index 02c28fac..0000000
--- "a/infra/config/generated/builders/goma/Chromium Win Goma RBE ATS Staging \050clobber\051/properties.json"
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Win Goma RBE ATS Staging (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Win Goma RBE ATS Staging (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?staging",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Chromium Win Goma RBE ATS ToT/properties.json b/infra/config/generated/builders/goma/Chromium Win Goma RBE ATS ToT/properties.json
deleted file mode 100644
index 69ace2b..0000000
--- a/infra/config/generated/builders/goma/Chromium Win Goma RBE ATS ToT/properties.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Win Goma RBE ATS ToT",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "goma_client_candidate"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Win Goma RBE ATS ToT",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Chromium Win Goma RBE Staging \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Chromium Win Goma RBE Staging \050clobber\051/properties.json"
deleted file mode 100644
index cd8f864..0000000
--- "a/infra/config/generated/builders/goma/Chromium Win Goma RBE Staging \050clobber\051/properties.json"
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Win Goma RBE Staging (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "clobber"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Win Goma RBE Staging (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?staging",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Chromium Win Goma RBE ToT/properties.json b/infra/config/generated/builders/goma/Chromium Win Goma RBE ToT/properties.json
deleted file mode 100644
index 36e79fa..0000000
--- a/infra/config/generated/builders/goma/Chromium Win Goma RBE ToT/properties.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium Win Goma RBE ToT",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_failfast",
-                  "goma_client_candidate"
-                ],
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium Win Goma RBE ToT",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Chromium iOS Goma RBE ToT/properties.json b/infra/config/generated/builders/goma/Chromium iOS Goma RBE ToT/properties.json
deleted file mode 100644
index 54ea858..0000000
--- a/infra/config/generated/builders/goma/Chromium iOS Goma RBE ToT/properties.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Chromium iOS Goma RBE ToT",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "mac_toolchain",
-                  "goma_failfast",
-                  "goma_client_candidate",
-                  "clobber"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64,
-                "target_platform": "ios"
-              },
-              "legacy_gclient_config": {
-                "config": "ios"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Chromium iOS Goma RBE ToT",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "jobs": 80,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium",
-  "xcode_build_version": "14c18"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Linux Builder Goma RBE Latest Client/properties.json b/infra/config/generated/builders/goma/Linux Builder Goma RBE Latest Client/properties.json
deleted file mode 100644
index 83dab23..0000000
--- a/infra/config/generated/builders/goma/Linux Builder Goma RBE Latest Client/properties.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Linux Builder Goma RBE Latest Client",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_latest_client",
-                  "goma_use_local"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "use_clang_coverage"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Linux Builder Goma RBE Latest Client",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Mac Builder \050dbg\051 Goma RBE Latest Client \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Mac Builder \050dbg\051 Goma RBE Latest Client \050clobber\051/properties.json"
deleted file mode 100644
index 5a07db34..0000000
--- "a/infra/config/generated/builders/goma/Mac Builder \050dbg\051 Goma RBE Latest Client \050clobber\051/properties.json"
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Mac Builder (dbg) Goma RBE Latest Client (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_latest_client",
-                  "clobber"
-                ],
-                "build_config": "Debug",
-                "config": "chromium",
-                "target_bits": 64,
-                "target_platform": "mac"
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Mac Builder (dbg) Goma RBE Latest Client (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "jobs": 80,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Win Builder \050dbg\051 Goma RBE ATS Latest Client/properties.json" "b/infra/config/generated/builders/goma/Win Builder \050dbg\051 Goma RBE ATS Latest Client/properties.json"
deleted file mode 100644
index 50159f3..0000000
--- "a/infra/config/generated/builders/goma/Win Builder \050dbg\051 Goma RBE ATS Latest Client/properties.json"
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Win Builder (dbg) Goma RBE ATS Latest Client",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_latest_client"
-                ],
-                "build_config": "Debug",
-                "config": "chromium",
-                "target_bits": 32
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Win Builder (dbg) Goma RBE ATS Latest Client",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Win Builder \050dbg\051 Goma RBE Latest Client/properties.json" "b/infra/config/generated/builders/goma/Win Builder \050dbg\051 Goma RBE Latest Client/properties.json"
deleted file mode 100644
index cd54a1a..0000000
--- "a/infra/config/generated/builders/goma/Win Builder \050dbg\051 Goma RBE Latest Client/properties.json"
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Win Builder (dbg) Goma RBE Latest Client",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_latest_client"
-                ],
-                "build_config": "Debug",
-                "config": "chromium",
-                "target_bits": 32
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Win Builder (dbg) Goma RBE Latest Client",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Win Builder Goma RBE ATS Latest Client/properties.json b/infra/config/generated/builders/goma/Win Builder Goma RBE ATS Latest Client/properties.json
deleted file mode 100644
index 93bdf2fe..0000000
--- a/infra/config/generated/builders/goma/Win Builder Goma RBE ATS Latest Client/properties.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Win Builder Goma RBE ATS Latest Client",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "goma_enable_global_file_stat_cache",
-                  "mb",
-                  "goma_latest_client",
-                  "goma_use_local"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 32
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Win Builder Goma RBE ATS Latest Client",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/goma/Win Builder Goma RBE Canary \050clobber\051/properties.json" "b/infra/config/generated/builders/goma/Win Builder Goma RBE Canary \050clobber\051/properties.json"
deleted file mode 100644
index c3334a11..0000000
--- "a/infra/config/generated/builders/goma/Win Builder Goma RBE Canary \050clobber\051/properties.json"
+++ /dev/null
@@ -1,58 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Win Builder Goma RBE Canary (clobber)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "goma_enable_global_file_stat_cache",
-                  "mb",
-                  "goma_canary",
-                  "goma_use_local",
-                  "clobber"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 32
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Win Builder Goma RBE Canary (clobber)",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/Win Builder Goma RBE Latest Client/properties.json b/infra/config/generated/builders/goma/Win Builder Goma RBE Latest Client/properties.json
deleted file mode 100644
index 41a2460b..0000000
--- a/infra/config/generated/builders/goma/Win Builder Goma RBE Latest Client/properties.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "Win Builder Goma RBE Latest Client",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "goma_enable_global_file_stat_cache",
-                  "mb",
-                  "goma_latest_client",
-                  "goma_use_local"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 32
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "Win Builder Goma RBE Latest Client",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": false,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-ats-latest/properties.json b/infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-ats-latest/properties.json
deleted file mode 100644
index bed69bd3..0000000
--- a/infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-ats-latest/properties.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "android-archive-dbg-goma-rbe-ats-latest",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_android_config": {
-                "config": "main_builder"
-              },
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "download_vr_test_apks",
-                  "goma_latest_client"
-                ],
-                "build_config": "Debug",
-                "config": "android",
-                "target_bits": 32,
-                "target_platform": "android"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "android"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "android-archive-dbg-goma-rbe-ats-latest",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-latest/properties.json b/infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-latest/properties.json
deleted file mode 100644
index b0fc45b..0000000
--- a/infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-latest/properties.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "android-archive-dbg-goma-rbe-latest",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_android_config": {
-                "config": "main_builder"
-              },
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "download_vr_test_apks",
-                  "goma_latest_client"
-                ],
-                "build_config": "Debug",
-                "config": "android",
-                "target_bits": 32,
-                "target_platform": "android"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "android"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "android-archive-dbg-goma-rbe-latest",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-latest/properties.json b/infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-latest/properties.json
deleted file mode 100644
index 9b20ceb..0000000
--- a/infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-latest/properties.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "chromeos-amd64-generic-rel-goma-rbe-latest",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_latest_client"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "cros_boards_with_qemu_images": [
-                  "amd64-generic-vm"
-                ],
-                "target_arch": "intel",
-                "target_bits": 64,
-                "target_cros_boards": [
-                  "amd64-generic"
-                ],
-                "target_platform": "chromeos"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "chromeos",
-                  "checkout_lacros_sdk"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "chromeos-amd64-generic-rel-goma-rbe-latest",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-tot/properties.json b/infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-tot/properties.json
deleted file mode 100644
index b1a387c..0000000
--- a/infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-tot/properties.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "chromeos-amd64-generic-rel-goma-rbe-tot",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-chromiumos-archive",
-              "builder_group": "chromium.goma",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_client_candidate"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "cros_boards_with_qemu_images": [
-                  "amd64-generic-vm"
-                ],
-                "target_arch": "intel",
-                "target_bits": 64,
-                "target_cros_boards": [
-                  "amd64-generic"
-                ],
-                "target_platform": "chromeos"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "chromeos",
-                  "checkout_lacros_sdk"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "chromeos-amd64-generic-rel-goma-rbe-tot",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?tot",
-    "server_host": "staging-goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/ios-device-goma-rbe-latest-clobber/properties.json b/infra/config/generated/builders/goma/ios-device-goma-rbe-latest-clobber/properties.json
deleted file mode 100644
index defa3a9..0000000
--- a/infra/config/generated/builders/goma/ios-device-goma-rbe-latest-clobber/properties.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "ios-device-goma-rbe-latest-clobber",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "mac_toolchain",
-                  "goma_latest_client",
-                  "clobber"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64,
-                "target_platform": "ios"
-              },
-              "legacy_gclient_config": {
-                "config": "ios"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "ios-device-goma-rbe-latest-clobber",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium",
-  "xcode_build_version": "14c18"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-ats-latest/properties.json b/infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-ats-latest/properties.json
deleted file mode 100644
index 0a8a3bb..0000000
--- a/infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-ats-latest/properties.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "linux-archive-rel-goma-rbe-ats-latest",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "clobber",
-                  "mb",
-                  "goma_latest_client"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "linux-archive-rel-goma-rbe-ats-latest",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-latest/properties.json b/infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-latest/properties.json
deleted file mode 100644
index 4ae4027..0000000
--- a/infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-latest/properties.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "linux-archive-rel-goma-rbe-latest",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "clobber",
-                  "mb",
-                  "goma_latest_client"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "linux-archive-rel-goma-rbe-latest",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/goma/mac-archive-rel-goma-rbe-latest/properties.json b/infra/config/generated/builders/goma/mac-archive-rel-goma-rbe-latest/properties.json
deleted file mode 100644
index 1d29d52..0000000
--- a/infra/config/generated/builders/goma/mac-archive-rel-goma-rbe-latest/properties.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "goma",
-              "builder": "mac-archive-rel-goma-rbe-latest",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-fyi-archive",
-              "builder_group": "chromium.goma.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "clobber",
-                  "mb",
-                  "goma_use_local",
-                  "goma_latest_client"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "goma",
-          "builder": "mac-archive-rel-goma-rbe-latest",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "jobs": 80,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.goma.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 51d635e..0bf7f6e7 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -33856,7 +33856,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios16-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
+      dimensions: "os:Mac-13"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -51066,102 +51066,6 @@
       }
     }
     builders {
-      name: "Chromium Android ARM 32-bit Goma RBE ToT"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Android ARM 32-bit Goma RBE ToT"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 18000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Chromium Android ARM 32-bit Goma RBE ToT (ATS)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Android ARM 32-bit Goma RBE ToT (ATS)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Android ARM 32-bit Goma RBE ToT (ATS)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 18000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Chromium Linux Goma RBE Staging"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Chromium Linux Goma RBE Staging"
@@ -51210,54 +51114,6 @@
       }
     }
     builders {
-      name: "Chromium Linux Goma RBE Staging (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Linux Goma RBE Staging (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Chromium Linux Goma RBE Staging (dbg)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Chromium Linux Goma RBE Staging (dbg)"
@@ -51306,150 +51162,6 @@
       }
     }
     builders {
-      name: "Chromium Linux Goma RBE Staging (dbg) (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Linux Goma RBE Staging (dbg) (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Linux Goma RBE Staging (dbg) (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Chromium Linux Goma RBE ToT"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Linux Goma RBE ToT"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Chromium Linux Goma RBE ToT (ATS)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Linux Goma RBE ToT (ATS)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Linux Goma RBE ToT (ATS)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Chromium Mac Goma RBE Staging"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Chromium Mac Goma RBE Staging"
@@ -51498,54 +51210,6 @@
       }
     }
     builders {
-      name: "Chromium Mac Goma RBE Staging (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Mac Goma RBE Staging (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Mac Goma RBE Staging (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Chromium Mac Goma RBE Staging (dbg)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Chromium Mac Goma RBE Staging (dbg)"
@@ -51594,54 +51258,6 @@
       }
     }
     builders {
-      name: "Chromium Mac Goma RBE ToT"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Mac Goma RBE ToT"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Mac Goma RBE ToT/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Chromium Win Goma RBE ATS Staging"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Chromium Win Goma RBE ATS Staging"
@@ -51690,102 +51306,6 @@
       }
     }
     builders {
-      name: "Chromium Win Goma RBE ATS Staging (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Win Goma RBE ATS Staging (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Win Goma RBE ATS Staging (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 14400
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Chromium Win Goma RBE ATS ToT"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Win Goma RBE ATS ToT"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Win Goma RBE ATS ToT/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 14400
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Chromium Win Goma RBE Staging"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Chromium Win Goma RBE Staging"
@@ -51834,154 +51354,6 @@
       }
     }
     builders {
-      name: "Chromium Win Goma RBE Staging (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Win Goma RBE Staging (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Win Goma RBE Staging (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 14400
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Chromium Win Goma RBE ToT"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium Win Goma RBE ToT"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium Win Goma RBE ToT/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 14400
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Chromium iOS Goma RBE ToT"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Chromium iOS Goma RBE ToT"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Chromium iOS Goma RBE ToT/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "xcode_ios_14c18"
-        path: "xcode_ios_14c18.app"
-      }
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Linux Builder Goma RBE Canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -52032,56 +51404,6 @@
       }
     }
     builders {
-      name: "Linux Builder Goma RBE Latest Client"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Linux Builder Goma RBE Latest Client/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Mac Builder (dbg) Goma RBE Canary (clobber)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac Builder (dbg) Goma RBE Canary (clobber)"
@@ -52130,54 +51452,6 @@
       }
     }
     builders {
-      name: "Mac Builder (dbg) Goma RBE Latest Client (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Mac Builder (dbg) Goma RBE Latest Client (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Mac Builder (dbg) Goma RBE Latest Client (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Mac M1 Builder (dbg) Goma RBE Canary (clobber)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac M1 Builder (dbg) Goma RBE Canary (clobber)"
@@ -52274,54 +51548,6 @@
       }
     }
     builders {
-      name: "Win Builder (dbg) Goma RBE ATS Latest Client"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Win Builder (dbg) Goma RBE ATS Latest Client"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Win Builder (dbg) Goma RBE ATS Latest Client/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Win Builder (dbg) Goma RBE Canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Win Builder (dbg) Goma RBE Canary"
@@ -52370,54 +51596,6 @@
       }
     }
     builders {
-      name: "Win Builder (dbg) Goma RBE Latest Client"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Win Builder (dbg) Goma RBE Latest Client"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Win Builder (dbg) Goma RBE Latest Client/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Win Builder Goma RBE ATS Canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Win Builder Goma RBE ATS Canary"
@@ -52466,54 +51644,6 @@
       }
     }
     builders {
-      name: "Win Builder Goma RBE ATS Latest Client"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Win Builder Goma RBE ATS Latest Client"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Win Builder Goma RBE ATS Latest Client/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "Win Builder Goma RBE Canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Win Builder Goma RBE Canary"
@@ -52562,102 +51692,6 @@
       }
     }
     builders {
-      name: "Win Builder Goma RBE Canary (clobber)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Win Builder Goma RBE Canary (clobber)"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Win Builder Goma RBE Canary (clobber)/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
-      name: "Win Builder Goma RBE Latest Client"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:Win Builder Goma RBE Latest Client"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/Win Builder Goma RBE Latest Client/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "android-archive-dbg-goma-rbe-ats-canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -52708,56 +51742,6 @@
       }
     }
     builders {
-      name: "android-archive-dbg-goma-rbe-ats-latest"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-ats-latest/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "android-archive-dbg-goma-rbe-canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -52808,56 +51792,6 @@
       }
     }
     builders {
-      name: "android-archive-dbg-goma-rbe-latest"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/android-archive-dbg-goma-rbe-latest/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "chromeos-amd64-generic-rel-goma-rbe-canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -52908,56 +51842,6 @@
       }
     }
     builders {
-      name: "chromeos-amd64-generic-rel-goma-rbe-latest"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-latest/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "chromeos-amd64-generic-rel-goma-rbe-staging"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:chromeos-amd64-generic-rel-goma-rbe-staging"
@@ -53006,54 +51890,6 @@
       }
     }
     builders {
-      name: "chromeos-amd64-generic-rel-goma-rbe-tot"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:chromeos-amd64-generic-rel-goma-rbe-tot"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/chromeos-amd64-generic-rel-goma-rbe-tot/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "ios-device-goma-rbe-canary-clobber"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-device-goma-rbe-canary-clobber"
@@ -53106,58 +51942,6 @@
       }
     }
     builders {
-      name: "ios-device-goma-rbe-latest-clobber"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:ios-device-goma-rbe-latest-clobber"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/ios-device-goma-rbe-latest-clobber/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      caches {
-        name: "xcode_ios_14c18"
-        path: "xcode_ios_14c18.app"
-      }
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "linux-archive-rel-goma-rbe-ats-canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -53208,56 +51992,6 @@
       }
     }
     builders {
-      name: "linux-archive-rel-goma-rbe-ats-latest"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-ats-latest/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "linux-archive-rel-goma-rbe-canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -53308,56 +52042,6 @@
       }
     }
     builders {
-      name: "linux-archive-rel-goma-rbe-latest"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/linux-archive-rel-goma-rbe-latest/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
-    builders {
       name: "mac-archive-rel-goma-rbe-canary"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-archive-rel-goma-rbe-canary"
@@ -53405,54 +52089,6 @@
         enable: true
       }
     }
-    builders {
-      name: "mac-archive-rel-goma-rbe-latest"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:mac-archive-rel-goma-rbe-latest"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/goma/mac-archive-rel-goma-rbe-latest/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.goma.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "goma-release-testing@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-      }
-    }
   }
 }
 buckets {
@@ -57216,7 +55852,7 @@
       dimensions: "builder:android-12-x64-rel-compilator"
       dimensions: "cores:32"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-22.04"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -72019,7 +70655,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios16-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-12"
+      dimensions: "os:Mac-13"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 7ab7d21..3b1d0e0c 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -9926,68 +9926,14 @@
   refs: "regexp:refs/heads/main"
   manifest_name: "REVISION"
   builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE ToT"
-    category: "rbe|tot|linux|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE ToT (ATS)"
-    category: "rbe|tot|linux|rel"
-    short_name: "ats"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Mac Goma RBE ToT"
-    category: "rbe|tot|mac|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Win Goma RBE ToT"
-    category: "rbe|tot|win|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Win Goma RBE ATS ToT"
-    category: "rbe|tot|win|rel"
-    short_name: "ats"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium iOS Goma RBE ToT"
-    category: "rbe|tot|ios|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Android ARM 32-bit Goma RBE ToT"
-    category: "rbe|tot|android arm|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Android ARM 32-bit Goma RBE ToT (ATS)"
-    category: "rbe|tot|android arm|rel"
-    short_name: "ats"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/chromeos-amd64-generic-rel-goma-rbe-tot"
-    category: "rbe|tot|cros|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging (clobber)"
-    category: "rbe|staging|linux|rel"
-    short_name: "clb"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging"
     category: "rbe|staging|linux|rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging (dbg) (clobber)"
-    category: "rbe|staging|linux|debug"
-    short_name: "clb"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging (dbg)"
     category: "rbe|staging|linux|debug"
   }
   builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Mac Goma RBE Staging (clobber)"
-    category: "rbe|staging|mac|rel"
-    short_name: "clb"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Chromium Mac Goma RBE Staging"
     category: "rbe|staging|mac|rel"
   }
@@ -10005,16 +9951,6 @@
     short_name: "ats"
   }
   builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Win Goma RBE Staging (clobber)"
-    category: "rbe|staging|win|rel"
-    short_name: "clb"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Win Goma RBE ATS Staging (clobber)"
-    category: "rbe|staging|win|rel"
-    short_name: "ats"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Chromium Android ARM 32-bit Goma RBE Staging"
     category: "rbe|staging|android arm|rel"
   }
@@ -10405,11 +10341,6 @@
     category: "rbe|win|rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.goma/Win Builder Goma RBE Canary (clobber)"
-    category: "rbe|win|rel"
-    short_name: "clb"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Win Builder (dbg) Goma RBE Canary"
     category: "rbe|win|dbg"
   }
@@ -16681,396 +16612,6 @@
   builder_view_only: true
 }
 consoles {
-  id: "goma.latest"
-  name: "goma.latest"
-  repo_url: "https://chromium.googlesource.com/chromium/src"
-  refs: "regexp:refs/heads/main"
-  manifest_name: "REVISION"
-  builders {
-    name: "buildbucket/luci.chromium.goma/linux-archive-rel-goma-rbe-latest"
-    category: "rbe|linux|rel"
-    short_name: "clb"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/linux-archive-rel-goma-rbe-ats-latest"
-    category: "rbe|linux|rel"
-    short_name: "ats"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Linux Builder Goma RBE Latest Client"
-    category: "rbe|linux|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/chromeos-amd64-generic-rel-goma-rbe-latest"
-    category: "rbe|cros|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/android-archive-dbg-goma-rbe-latest"
-    category: "rbe|android|dbg"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/android-archive-dbg-goma-rbe-ats-latest"
-    category: "rbe|android|dbg"
-    short_name: "ats"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/mac-archive-rel-goma-rbe-latest"
-    category: "rbe|mac|rel"
-    short_name: "clb"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Mac Builder (dbg) Goma RBE Latest Client (clobber)"
-    category: "rbe|mac|dbg"
-    short_name: "clb"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/ios-device-goma-rbe-latest-clobber"
-    category: "rbe|ios"
-    short_name: "clb"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Win Builder Goma RBE Latest Client"
-    category: "rbe|win|rel"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Win Builder (dbg) Goma RBE Latest Client"
-    category: "rbe|win|dbg"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Win Builder Goma RBE ATS Latest Client"
-    category: "rbe|win|rel"
-    short_name: "ats"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.goma/Win Builder (dbg) Goma RBE ATS Latest Client"
-    category: "rbe|win|dbg"
-    short_name: "ats"
-  }
-  header {
-    oncalls {
-      name: "Chromium"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
-    }
-    oncalls {
-      name: "Android"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-android-sheriff"
-    }
-    oncalls {
-      name: "iOS"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-ios"
-    }
-    oncalls {
-      name: "ChromeOS"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chromeos-gardeners"
-    }
-    oncalls {
-      name: "Fuchsia"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:chrome-fuchsia-gardener"
-    }
-    oncalls {
-      name: "GPU"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-gpu-pixel-wrangler-weekly"
-    }
-    oncalls {
-      name: "ANGLE"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:angle-wrangler"
-    }
-    oncalls {
-      name: "Perf"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:chromium-perf-regression-sheriff"
-    }
-    oncalls {
-      name: "Perfbot"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:chromium-perf-bot-sheriff"
-    }
-    oncalls {
-      name: "Trooper"
-      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-ops-client-infra"
-      show_primary_secondary_labels: true
-    }
-    links {
-      name: "Builds"
-      links {
-        text: "continuous"
-        url: "https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html"
-        alt: "Continuous browser snapshots"
-      }
-      links {
-        text: "symbols"
-        url: "https://www.chromium.org/developers/how-tos/debugging-on-windows"
-        alt: "Windows Symbols"
-      }
-      links {
-        text: "status"
-        url: "https://chromium-status.appspot.com/"
-        alt: "Current tree status"
-      }
-    }
-    links {
-      name: "Dashboards"
-      links {
-        text: "perf"
-        url: "https://chromeperf.appspot.com/"
-        alt: "Chrome perf dashboard"
-      }
-      links {
-        text: "LUCI Analysis"
-        url: "https://luci-analysis.appspot.com"
-        alt: "New flake portal"
-      }
-    }
-    links {
-      name: "Chromium"
-      links {
-        text: "source"
-        url: "https://chromium.googlesource.com/chromium/src"
-        alt: "Chromium source code repository"
-      }
-      links {
-        text: "reviews"
-        url: "https://chromium-review.googlesource.com"
-        alt: "Chromium code review tool"
-      }
-      links {
-        text: "bugs"
-        url: "https://crbug.com"
-        alt: "Chromium bug tracker"
-      }
-      links {
-        text: "coverage"
-        url: "https://analysis.chromium.org/coverage/p/chromium"
-        alt: "Chromium code coverage dashboard"
-      }
-      links {
-        text: "dev"
-        url: "https://dev.chromium.org/Home"
-        alt: "Chromium developer home page"
-      }
-      links {
-        text: "support"
-        url: "https://support.google.com/chrome/#topic=7438008"
-        alt: "Google Chrome help center"
-      }
-    }
-    links {
-      name: "Consoles"
-      links {
-        text: "android"
-        url: "/p/chromium/g/chromium.android"
-        alt: "Chromium Android console"
-      }
-      links {
-        text: "clang"
-        url: "/p/chromium/g/chromium.clang"
-        alt: "Chromium Clang console"
-      }
-      links {
-        text: "dawn"
-        url: "/p/chromium/g/chromium.dawn"
-        alt: "Chromium Dawn console"
-      }
-      links {
-        text: "fuzz"
-        url: "/p/chromium/g/chromium.fuzz"
-        alt: "Chromium Fuzz console"
-      }
-      links {
-        text: "fuchsia"
-        url: "/p/chromium/g/chromium.fuchsia"
-        alt: "Chromium Fuchsia console"
-      }
-      links {
-        text: "fyi"
-        url: "/p/chromium/g/chromium.fyi"
-        alt: "Chromium FYI console"
-      }
-      links {
-        text: "gpu"
-        url: "/p/chromium/g/chromium.gpu"
-        alt: "Chromium GPU console"
-      }
-      links {
-        text: "infra"
-        url: "/p/chromium/g/chromium.infra"
-        alt: "Chromium Infra console"
-      }
-      links {
-        text: "memory.fyi"
-        url: "/p/chromium/g/chromium.memory.fyi"
-        alt: "Chromium Memory FYI console"
-      }
-      links {
-        text: "perf"
-        url: "/p/chrome/g/chrome.perf/console"
-        alt: "Chromium Perf console"
-      }
-      links {
-        text: "perf.fyi"
-        url: "/p/chrome/g/chrome.perf.fyi/console"
-        alt: "Chromium Perf FYI console"
-      }
-      links {
-        text: "angle"
-        url: "/p/chromium/g/chromium.angle"
-        alt: "Chromium ANGLE console"
-      }
-      links {
-        text: "swangle"
-        url: "/p/chromium/g/chromium.swangle"
-        alt: "Chromium SWANGLE console"
-      }
-      links {
-        text: "updater"
-        url: "/p/chromium/g/chromium.updater"
-        alt: "Chromium Updater console"
-      }
-      links {
-        text: "webrtc"
-        url: "/p/chromium/g/chromium.webrtc"
-        alt: "Chromium WebRTC console"
-      }
-      links {
-        text: "chromiumos"
-        url: "/p/chromium/g/chromium.chromiumos"
-        alt: "ChromiumOS console"
-      }
-      links {
-        text: "flakiness"
-        url: "/p/chromium/g/chromium.flakiness"
-        alt: "Chromium Flakiness console"
-      }
-    }
-    links {
-      name: "Branch Consoles"
-      links {
-        text: "m108"
-        url: "/p/chromium-m108/g/main/console"
-      }
-      links {
-        text: "m109"
-        url: "/p/chromium-m109/g/main/console"
-      }
-      links {
-        text: "m110"
-        url: "/p/chromium-m110/g/main/console"
-      }
-      links {
-        text: "m112"
-        url: "/p/chromium-m112/g/main/console"
-      }
-      links {
-        text: "m113"
-        url: "/p/chromium-m113/g/main/console"
-      }
-      links {
-        text: "m114"
-        url: "/p/chromium-m114/g/main/console"
-      }
-      links {
-        text: "trunk"
-        url: "/p/chromium/g/main/console"
-        alt: "Trunk (ToT) console"
-      }
-    }
-    links {
-      name: "Tryservers"
-      links {
-        text: "android"
-        url: "/p/chromium/g/tryserver.chromium.android/builders"
-        alt: "Android"
-      }
-      links {
-        text: "angle"
-        url: "/p/chromium/g/tryserver.chromium.angle/builders"
-        alt: "Angle"
-      }
-      links {
-        text: "blink"
-        url: "/p/chromium/g/tryserver.blink/builders"
-        alt: "Blink"
-      }
-      links {
-        text: "chrome"
-        url: "/p/chrome/g/tryserver.chrome/builders"
-        alt: "Chrome"
-      }
-      links {
-        text: "chromiumos"
-        url: "/p/chromium/g/tryserver.chromium.chromiumos/builders"
-        alt: "ChromiumOS"
-      }
-      links {
-        text: "fuchsia"
-        url: "/p/chromium/g/tryserver.chromium.fuchsia/builders"
-        alt: "Fuchsia"
-      }
-      links {
-        text: "linux"
-        url: "/p/chromium/g/tryserver.chromium.linux/builders"
-        alt: "Linux"
-      }
-      links {
-        text: "mac"
-        url: "/p/chromium/g/tryserver.chromium.mac/builders"
-        alt: "Mac"
-      }
-      links {
-        text: "swangle"
-        url: "/p/chromium/g/tryserver.chromium.swangle/builders"
-        alt: "SWANGLE"
-      }
-      links {
-        text: "tricium"
-        url: "/p/chromium/g/tryserver.chromium.tricium/builders"
-        alt: "Tricium"
-      }
-      links {
-        text: "win"
-        url: "/p/chromium/g/tryserver.chromium.win/builders"
-        alt: "Win"
-      }
-    }
-    links {
-      name: "Navigate"
-      links {
-        text: "about"
-        url: "http://dev.chromium.org/developers/testing/chromium-build-infrastructure/tour-of-the-chromium-buildbot"
-        alt: "Tour of the console"
-      }
-      links {
-        text: "customize"
-        url: "https://chromium.googlesource.com/chromium/src/+/refs/heads/main/infra/config/generated/luci/luci-milo.cfg"
-        alt: "Customize this console"
-      }
-    }
-    console_groups {
-      title {
-        text: "Tree Closers"
-        url: "https://chromium-status.appspot.com/"
-      }
-      console_ids: "chromium/chromium"
-      console_ids: "chromium/chromium.win"
-      console_ids: "chromium/chromium.mac"
-      console_ids: "chromium/chromium.linux"
-      console_ids: "chromium/chromium.chromiumos"
-      console_ids: "chromium/chromium.fuchsia"
-      console_ids: "chrome/chrome"
-      console_ids: "chromium/chromium.memory"
-      console_ids: "chromium/chromium.gpu"
-    }
-    console_groups {
-      console_ids: "chromium/chromium.android"
-      console_ids: "chrome/chrome.perf"
-      console_ids: "chromium/chromium.fuchsia.fyi"
-      console_ids: "chromium/chromium.gpu.fyi"
-      console_ids: "chromium/chromium.angle"
-      console_ids: "chromium/chromium.swangle"
-      console_ids: "chromium/chromium.fuzz"
-    }
-    tree_status_host: "chromium-status.appspot.com"
-  }
-}
-consoles {
   id: "infra"
   name: "infra"
   repo_url: "https://chromium.googlesource.com/chromium/src"
@@ -17089,20 +16630,10 @@
   refs: "regexp:refs/heads/main"
   manifest_name: "REVISION"
   builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging (clobber)"
-    category: "rbe|rel"
-    short_name: "clb"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging"
     category: "rbe|rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging (dbg) (clobber)"
-    category: "rbe|debug"
-    short_name: "clb"
-  }
-  builders {
     name: "buildbucket/luci.chromium.goma/Chromium Linux Goma RBE Staging (dbg)"
     category: "rbe|debug"
   }
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 0c81294..ee3dd3a 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -411,24 +411,6 @@
   }
 }
 job {
-  id: "Chromium Android ARM 32-bit Goma RBE ToT"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Android ARM 32-bit Goma RBE ToT"
-  }
-}
-job {
-  id: "Chromium Android ARM 32-bit Goma RBE ToT (ATS)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Android ARM 32-bit Goma RBE ToT (ATS)"
-  }
-}
-job {
   id: "Chromium Linux Goma RBE Staging"
   realm: "goma"
   buildbucket {
@@ -438,15 +420,6 @@
   }
 }
 job {
-  id: "Chromium Linux Goma RBE Staging (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Linux Goma RBE Staging (clobber)"
-  }
-}
-job {
   id: "Chromium Linux Goma RBE Staging (dbg)"
   realm: "goma"
   buildbucket {
@@ -456,33 +429,6 @@
   }
 }
 job {
-  id: "Chromium Linux Goma RBE Staging (dbg) (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Linux Goma RBE Staging (dbg) (clobber)"
-  }
-}
-job {
-  id: "Chromium Linux Goma RBE ToT"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Linux Goma RBE ToT"
-  }
-}
-job {
-  id: "Chromium Linux Goma RBE ToT (ATS)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Linux Goma RBE ToT (ATS)"
-  }
-}
-job {
   id: "Chromium Mac Goma RBE Staging"
   realm: "goma"
   buildbucket {
@@ -492,15 +438,6 @@
   }
 }
 job {
-  id: "Chromium Mac Goma RBE Staging (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Mac Goma RBE Staging (clobber)"
-  }
-}
-job {
   id: "Chromium Mac Goma RBE Staging (dbg)"
   realm: "goma"
   buildbucket {
@@ -510,15 +447,6 @@
   }
 }
 job {
-  id: "Chromium Mac Goma RBE ToT"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Mac Goma RBE ToT"
-  }
-}
-job {
   id: "Chromium Win Goma RBE ATS Staging"
   realm: "goma"
   buildbucket {
@@ -528,24 +456,6 @@
   }
 }
 job {
-  id: "Chromium Win Goma RBE ATS Staging (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Win Goma RBE ATS Staging (clobber)"
-  }
-}
-job {
-  id: "Chromium Win Goma RBE ATS ToT"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Win Goma RBE ATS ToT"
-  }
-}
-job {
   id: "Chromium Win Goma RBE Staging"
   realm: "goma"
   buildbucket {
@@ -555,33 +465,6 @@
   }
 }
 job {
-  id: "Chromium Win Goma RBE Staging (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Win Goma RBE Staging (clobber)"
-  }
-}
-job {
-  id: "Chromium Win Goma RBE ToT"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium Win Goma RBE ToT"
-  }
-}
-job {
-  id: "Chromium iOS Goma RBE ToT"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Chromium iOS Goma RBE ToT"
-  }
-}
-job {
   id: "ChromiumOS ASAN Release"
   realm: "ci"
   triggering_policy {
@@ -1586,15 +1469,6 @@
   }
 }
 job {
-  id: "Linux Builder Goma RBE Latest Client"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Linux Builder Goma RBE Latest Client"
-  }
-}
-job {
   id: "Linux Builder reclient staging"
   realm: "reclient"
   buildbucket {
@@ -1957,15 +1831,6 @@
   }
 }
 job {
-  id: "Mac Builder (dbg) Goma RBE Latest Client (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Mac Builder (dbg) Goma RBE Latest Client (clobber)"
-  }
-}
-job {
   id: "Mac Builder (reclient compare)"
   realm: "ci"
   schedule: "@daily"
@@ -3035,15 +2900,6 @@
   }
 }
 job {
-  id: "Win Builder (dbg) Goma RBE ATS Latest Client"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Win Builder (dbg) Goma RBE ATS Latest Client"
-  }
-}
-job {
   id: "Win Builder (dbg) Goma RBE Canary"
   realm: "goma"
   buildbucket {
@@ -3053,15 +2909,6 @@
   }
 }
 job {
-  id: "Win Builder (dbg) Goma RBE Latest Client"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Win Builder (dbg) Goma RBE Latest Client"
-  }
-}
-job {
   id: "Win Builder Goma RBE ATS Canary"
   realm: "goma"
   buildbucket {
@@ -3071,15 +2918,6 @@
   }
 }
 job {
-  id: "Win Builder Goma RBE ATS Latest Client"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Win Builder Goma RBE ATS Latest Client"
-  }
-}
-job {
   id: "Win Builder Goma RBE Canary"
   realm: "goma"
   buildbucket {
@@ -3089,24 +2927,6 @@
   }
 }
 job {
-  id: "Win Builder Goma RBE Canary (clobber)"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Win Builder Goma RBE Canary (clobber)"
-  }
-}
-job {
-  id: "Win Builder Goma RBE Latest Client"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "Win Builder Goma RBE Latest Client"
-  }
-}
-job {
   id: "Win x64 Builder"
   realm: "ci"
   buildbucket {
@@ -3459,15 +3279,6 @@
   }
 }
 job {
-  id: "android-archive-dbg-goma-rbe-ats-latest"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "android-archive-dbg-goma-rbe-ats-latest"
-  }
-}
-job {
   id: "android-archive-dbg-goma-rbe-canary"
   realm: "goma"
   buildbucket {
@@ -3477,15 +3288,6 @@
   }
 }
 job {
-  id: "android-archive-dbg-goma-rbe-latest"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "android-archive-dbg-goma-rbe-latest"
-  }
-}
-job {
   id: "android-archive-rel"
   realm: "ci"
   buildbucket {
@@ -4103,15 +3905,6 @@
   }
 }
 job {
-  id: "chromeos-amd64-generic-rel-goma-rbe-latest"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "chromeos-amd64-generic-rel-goma-rbe-latest"
-  }
-}
-job {
   id: "chromeos-amd64-generic-rel-goma-rbe-staging"
   realm: "goma"
   buildbucket {
@@ -4121,15 +3914,6 @@
   }
 }
 job {
-  id: "chromeos-amd64-generic-rel-goma-rbe-tot"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "chromeos-amd64-generic-rel-goma-rbe-tot"
-  }
-}
-job {
   id: "chromeos-arm-generic-dbg"
   realm: "ci"
   buildbucket {
@@ -4394,15 +4178,6 @@
   }
 }
 job {
-  id: "ios-device-goma-rbe-latest-clobber"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "ios-device-goma-rbe-latest-clobber"
-  }
-}
-job {
   id: "ios-fieldtrial-rel"
   realm: "ci"
   buildbucket {
@@ -4802,15 +4577,6 @@
   }
 }
 job {
-  id: "linux-archive-rel-goma-rbe-ats-latest"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "linux-archive-rel-goma-rbe-ats-latest"
-  }
-}
-job {
   id: "linux-archive-rel-goma-rbe-canary"
   realm: "goma"
   buildbucket {
@@ -4820,15 +4586,6 @@
   }
 }
 job {
-  id: "linux-archive-rel-goma-rbe-latest"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "linux-archive-rel-goma-rbe-latest"
-  }
-}
-job {
   id: "linux-ash-chromium-generator-rel"
   realm: "ci"
   schedule: "triggered"
@@ -5442,15 +5199,6 @@
   }
 }
 job {
-  id: "mac-archive-rel-goma-rbe-latest"
-  realm: "goma"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "goma"
-    builder: "mac-archive-rel-goma-rbe-latest"
-  }
-}
-job {
   id: "mac-arm64-archive-dbg"
   realm: "ci"
   buildbucket {
@@ -6751,55 +6499,27 @@
   triggers: "win32-updater-builder-dbg"
   triggers: "win32-updater-builder-rel"
   triggers: "Chromium Android ARM 32-bit Goma RBE Staging"
-  triggers: "Chromium Android ARM 32-bit Goma RBE ToT"
-  triggers: "Chromium Android ARM 32-bit Goma RBE ToT (ATS)"
   triggers: "Chromium Linux Goma RBE Staging"
-  triggers: "Chromium Linux Goma RBE Staging (clobber)"
   triggers: "Chromium Linux Goma RBE Staging (dbg)"
-  triggers: "Chromium Linux Goma RBE Staging (dbg) (clobber)"
-  triggers: "Chromium Linux Goma RBE ToT"
-  triggers: "Chromium Linux Goma RBE ToT (ATS)"
   triggers: "Chromium Mac Goma RBE Staging"
-  triggers: "Chromium Mac Goma RBE Staging (clobber)"
   triggers: "Chromium Mac Goma RBE Staging (dbg)"
-  triggers: "Chromium Mac Goma RBE ToT"
   triggers: "Chromium Win Goma RBE ATS Staging"
-  triggers: "Chromium Win Goma RBE ATS Staging (clobber)"
-  triggers: "Chromium Win Goma RBE ATS ToT"
   triggers: "Chromium Win Goma RBE Staging"
-  triggers: "Chromium Win Goma RBE Staging (clobber)"
-  triggers: "Chromium Win Goma RBE ToT"
-  triggers: "Chromium iOS Goma RBE ToT"
   triggers: "Linux Builder Goma RBE Canary"
-  triggers: "Linux Builder Goma RBE Latest Client"
   triggers: "Mac Builder (dbg) Goma RBE Canary (clobber)"
-  triggers: "Mac Builder (dbg) Goma RBE Latest Client (clobber)"
   triggers: "Mac M1 Builder (dbg) Goma RBE Canary (clobber)"
   triggers: "Win Builder (dbg) Goma RBE ATS Canary"
-  triggers: "Win Builder (dbg) Goma RBE ATS Latest Client"
   triggers: "Win Builder (dbg) Goma RBE Canary"
-  triggers: "Win Builder (dbg) Goma RBE Latest Client"
   triggers: "Win Builder Goma RBE ATS Canary"
-  triggers: "Win Builder Goma RBE ATS Latest Client"
   triggers: "Win Builder Goma RBE Canary"
-  triggers: "Win Builder Goma RBE Canary (clobber)"
-  triggers: "Win Builder Goma RBE Latest Client"
   triggers: "android-archive-dbg-goma-rbe-ats-canary"
-  triggers: "android-archive-dbg-goma-rbe-ats-latest"
   triggers: "android-archive-dbg-goma-rbe-canary"
-  triggers: "android-archive-dbg-goma-rbe-latest"
   triggers: "chromeos-amd64-generic-rel-goma-rbe-canary"
-  triggers: "chromeos-amd64-generic-rel-goma-rbe-latest"
   triggers: "chromeos-amd64-generic-rel-goma-rbe-staging"
-  triggers: "chromeos-amd64-generic-rel-goma-rbe-tot"
   triggers: "ios-device-goma-rbe-canary-clobber"
-  triggers: "ios-device-goma-rbe-latest-clobber"
   triggers: "linux-archive-rel-goma-rbe-ats-canary"
-  triggers: "linux-archive-rel-goma-rbe-ats-latest"
   triggers: "linux-archive-rel-goma-rbe-canary"
-  triggers: "linux-archive-rel-goma-rbe-latest"
   triggers: "mac-archive-rel-goma-rbe-canary"
-  triggers: "mac-archive-rel-goma-rbe-latest"
   triggers: "Comparison Linux (reclient vs reclient remote links)"
   triggers: "Comparison Linux (reclient vs reclient remote links)(small)"
   triggers: "Linux Builder (canonical wd) (reclient compare)"
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 763369fa..c2e50b8 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -98,10 +98,6 @@
             "server_host": "staging-goma.chromium.org",
             "rpc_extra_params": "?staging",
         },
-        RBE_TOT = {
-            "server_host": "staging-goma.chromium.org",
-            "rpc_extra_params": "?tot",
-        },
     ),
     jobs = struct(
         # This is for 4 cores mac. -j40 is too small, especially for clobber
diff --git a/infra/config/lib/linux-default.json b/infra/config/lib/linux-default.json
index 1cf19afa..dff9f41 100644
--- a/infra/config/lib/linux-default.json
+++ b/infra/config/lib/linux-default.json
@@ -12,6 +12,7 @@
   },
   "try": {
     "android-12-x64-rel": "Ubuntu-22.04",
+    "android-12-x64-rel-compilator": "Ubuntu-22.04",
     "android-arm64-rel": "Ubuntu-22.04",
     "android-nougat-x86-rel": "Ubuntu-22.04",
     "android-x64-cast": "Ubuntu-22.04",
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 15f0776..8b66806 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -2460,7 +2460,7 @@
         ),
         build_gs_bucket = "chromium-fyi-archive",
     ),
-    os = os.MAC_DEFAULT,
+    os = os.MAC_13,
     console_view_entry = consoles.console_view_entry(
         category = "iOS|iOS16",
         short_name = "ios16",
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 2f76b98e5..7221994 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -41,13 +41,13 @@
                 "findit-for-me@appspot.gserviceaccount.com",
                 "tricium-prod@appspot.gserviceaccount.com",
             ],
-            projects = [
-                "angle",
-                "dawn",
-                "skia",
-                "swiftshader",
-                "v8",
-            ] if settings.is_main else None,
+            projects = [p for p in [
+                branches.value(branch_selector = branches.selector.MAIN, value = "angle"),
+                branches.value(branch_selector = branches.selector.DESKTOP_BRANCHES, value = "dawn"),
+                branches.value(branch_selector = branches.selector.MAIN, value = "skia"),
+                branches.value(branch_selector = branches.selector.MAIN, value = "swiftshader"),
+                branches.value(branch_selector = branches.selector.MAIN, value = "v8"),
+            ] if p != None],
         ),
         acl.entry(
             roles = acl.BUILDBUCKET_OWNER,
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 606901c..dc5a261 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -534,7 +534,7 @@
     mirrors = [
         "ci/ios16-beta-simulator",
     ],
-    os = os.MAC_DEFAULT,
+    os = os.MAC_13,
     reclient_jobs = reclient.jobs.LOW_JOBS_FOR_CQ,
 )
 
diff --git a/infra/config/subprojects/goma/consoles/chromium.goma.fyi.star b/infra/config/subprojects/goma/consoles/chromium.goma.fyi.star
index 10ce8c0..44e05c3 100644
--- a/infra/config/subprojects/goma/consoles/chromium.goma.fyi.star
+++ b/infra/config/subprojects/goma/consoles/chromium.goma.fyi.star
@@ -61,11 +61,6 @@
             category = "rbe|win|rel",
         ),
         luci.console_view_entry(
-            builder = "goma/Win Builder Goma RBE Canary (clobber)",
-            category = "rbe|win|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
             builder = "goma/Win Builder (dbg) Goma RBE Canary",
             category = "rbe|win|dbg",
         ),
diff --git a/infra/config/subprojects/goma/consoles/chromium.goma.star b/infra/config/subprojects/goma/consoles/chromium.goma.star
index e6be711..df38459 100644
--- a/infra/config/subprojects/goma/consoles/chromium.goma.star
+++ b/infra/config/subprojects/goma/consoles/chromium.goma.star
@@ -10,68 +10,14 @@
     header = HEADER,
     entries = [
         luci.console_view_entry(
-            builder = "goma/Chromium Linux Goma RBE ToT",
-            category = "rbe|tot|linux|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Linux Goma RBE ToT (ATS)",
-            category = "rbe|tot|linux|rel",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Mac Goma RBE ToT",
-            category = "rbe|tot|mac|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Win Goma RBE ToT",
-            category = "rbe|tot|win|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Win Goma RBE ATS ToT",
-            category = "rbe|tot|win|rel",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium iOS Goma RBE ToT",
-            category = "rbe|tot|ios|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Android ARM 32-bit Goma RBE ToT",
-            category = "rbe|tot|android arm|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Android ARM 32-bit Goma RBE ToT (ATS)",
-            category = "rbe|tot|android arm|rel",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
-            builder = "goma/chromeos-amd64-generic-rel-goma-rbe-tot",
-            category = "rbe|tot|cros|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Linux Goma RBE Staging (clobber)",
-            category = "rbe|staging|linux|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
             builder = "goma/Chromium Linux Goma RBE Staging",
             category = "rbe|staging|linux|rel",
         ),
         luci.console_view_entry(
-            builder = "goma/Chromium Linux Goma RBE Staging (dbg) (clobber)",
-            category = "rbe|staging|linux|debug",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
             builder = "goma/Chromium Linux Goma RBE Staging (dbg)",
             category = "rbe|staging|linux|debug",
         ),
         luci.console_view_entry(
-            builder = "goma/Chromium Mac Goma RBE Staging (clobber)",
-            category = "rbe|staging|mac|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
             builder = "goma/Chromium Mac Goma RBE Staging",
             category = "rbe|staging|mac|rel",
         ),
@@ -89,16 +35,6 @@
             short_name = "ats",
         ),
         luci.console_view_entry(
-            builder = "goma/Chromium Win Goma RBE Staging (clobber)",
-            category = "rbe|staging|win|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Chromium Win Goma RBE ATS Staging (clobber)",
-            category = "rbe|staging|win|rel",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
             builder = "goma/Chromium Android ARM 32-bit Goma RBE Staging",
             category = "rbe|staging|android arm|rel",
         ),
diff --git a/infra/config/subprojects/goma/consoles/goma.latest.star b/infra/config/subprojects/goma/consoles/goma.latest.star
deleted file mode 100644
index 016464e7..0000000
--- a/infra/config/subprojects/goma/consoles/goma.latest.star
+++ /dev/null
@@ -1,73 +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.
-
-load("//console-header.star", "HEADER")
-
-luci.console_view(
-    name = "goma.latest",
-    repo = "https://chromium.googlesource.com/chromium/src",
-    header = HEADER,
-    entries = [
-        luci.console_view_entry(
-            builder = "goma/linux-archive-rel-goma-rbe-latest",
-            category = "rbe|linux|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
-            builder = "goma/linux-archive-rel-goma-rbe-ats-latest",
-            category = "rbe|linux|rel",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Linux Builder Goma RBE Latest Client",
-            category = "rbe|linux|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/chromeos-amd64-generic-rel-goma-rbe-latest",
-            category = "rbe|cros|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/android-archive-dbg-goma-rbe-latest",
-            category = "rbe|android|dbg",
-        ),
-        luci.console_view_entry(
-            builder = "goma/android-archive-dbg-goma-rbe-ats-latest",
-            category = "rbe|android|dbg",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
-            builder = "goma/mac-archive-rel-goma-rbe-latest",
-            category = "rbe|mac|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Mac Builder (dbg) Goma RBE Latest Client (clobber)",
-            category = "rbe|mac|dbg",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
-            builder = "goma/ios-device-goma-rbe-latest-clobber",
-            category = "rbe|ios",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Win Builder Goma RBE Latest Client",
-            category = "rbe|win|rel",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Win Builder (dbg) Goma RBE Latest Client",
-            category = "rbe|win|dbg",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Win Builder Goma RBE ATS Latest Client",
-            category = "rbe|win|rel",
-            short_name = "ats",
-        ),
-        luci.console_view_entry(
-            builder = "goma/Win Builder (dbg) Goma RBE ATS Latest Client",
-            category = "rbe|win|dbg",
-            short_name = "ats",
-        ),
-    ],
-)
diff --git a/infra/config/subprojects/goma/consoles/luci.chromium.goma.star b/infra/config/subprojects/goma/consoles/luci.chromium.goma.star
index ca261a9..a70b36c 100644
--- a/infra/config/subprojects/goma/consoles/luci.chromium.goma.star
+++ b/infra/config/subprojects/goma/consoles/luci.chromium.goma.star
@@ -10,20 +10,10 @@
     header = HEADER,
     entries = [
         luci.console_view_entry(
-            builder = "goma/Chromium Linux Goma RBE Staging (clobber)",
-            category = "rbe|rel",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
             builder = "goma/Chromium Linux Goma RBE Staging",
             category = "rbe|rel",
         ),
         luci.console_view_entry(
-            builder = "goma/Chromium Linux Goma RBE Staging (dbg) (clobber)",
-            category = "rbe|debug",
-            short_name = "clb",
-        ),
-        luci.console_view_entry(
             builder = "goma/Chromium Linux Goma RBE Staging (dbg)",
             category = "rbe|debug",
         ),
diff --git a/infra/config/subprojects/goma/goma.star b/infra/config/subprojects/goma/goma.star
index 7b69dd9..88cb910 100644
--- a/infra/config/subprojects/goma/goma.star
+++ b/infra/config/subprojects/goma/goma.star
@@ -300,27 +300,6 @@
 )
 
 fyi_goma_rbe_canary_builder(
-    name = "Win Builder Goma RBE Canary (clobber)",
-    builder_spec = builder_config.copy_from(
-        "ci/Win Builder",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_canary",
-                    "goma_use_local",
-                    "clobber",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.WINDOWS_DEFAULT,
-    goma_enable_ats = False,
-)
-
-fyi_goma_rbe_canary_builder(
     name = "Win Builder (dbg) Goma RBE ATS Canary",
     builder_spec = builder_config.copy_from(
         "ci/Win Builder (dbg)",
@@ -359,275 +338,6 @@
     goma_enable_ats = True,
 )
 
-def fyi_goma_rbe_latest_client_builder(
-        *,
-        name,
-        goma_backend = goma.backend.RBE_PROD,
-        os = os.LINUX_DEFAULT,
-        **kwargs):
-    return builder(
-        name = name,
-        builder_group = "chromium.goma.fyi",
-        execution_timeout = 10 * time.hour,
-        goma_backend = goma_backend,
-        os = os,
-        **kwargs
-    )
-
-fyi_goma_rbe_latest_client_builder(
-    name = "Linux Builder Goma RBE Latest Client",
-    builder_spec = builder_config.copy_from(
-        "ci/Linux Builder",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                    "goma_use_local",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "Mac Builder (dbg) Goma RBE Latest Client (clobber)",
-    builder_spec = builder_config.copy_from(
-        "ci/Mac Builder (dbg)",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                    "clobber",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.MAC_DEFAULT,
-    goma_jobs = goma.jobs.J80,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "Win Builder (dbg) Goma RBE Latest Client",
-    builder_spec = builder_config.copy_from(
-        "ci/Win Builder (dbg)",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.WINDOWS_DEFAULT,
-    goma_enable_ats = False,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "Win Builder Goma RBE Latest Client",
-    builder_spec = builder_config.copy_from(
-        "ci/Win Builder",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                    "goma_use_local",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.WINDOWS_DEFAULT,
-    goma_enable_ats = False,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "Win Builder (dbg) Goma RBE ATS Latest Client",
-    builder_spec = builder_config.copy_from(
-        "ci/Win Builder (dbg)",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.WINDOWS_DEFAULT,
-    goma_enable_ats = True,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "Win Builder Goma RBE ATS Latest Client",
-    builder_spec = builder_config.copy_from(
-        "ci/Win Builder",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                    "goma_use_local",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.WINDOWS_DEFAULT,
-    goma_enable_ats = True,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "android-archive-dbg-goma-rbe-ats-latest",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(
-            config = "chromium",
-            apply_configs = ["android"],
-        ),
-        chromium_config = builder_config.chromium_config(
-            config = "android",
-            apply_configs = [
-                "mb",
-                "download_vr_test_apks",
-                "goma_latest_client",
-            ],
-            build_config = builder_config.build_config.DEBUG,
-            target_bits = 32,
-            target_platform = builder_config.target_platform.ANDROID,
-        ),
-        android_config = builder_config.android_config(config = "main_builder"),
-        build_gs_bucket = "chromium-fyi-archive",
-    ),
-    goma_enable_ats = True,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "android-archive-dbg-goma-rbe-latest",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(
-            config = "chromium",
-            apply_configs = ["android"],
-        ),
-        chromium_config = builder_config.chromium_config(
-            config = "android",
-            apply_configs = [
-                "mb",
-                "download_vr_test_apks",
-                "goma_latest_client",
-            ],
-            build_config = builder_config.build_config.DEBUG,
-            target_bits = 32,
-            target_platform = builder_config.target_platform.ANDROID,
-        ),
-        android_config = builder_config.android_config(config = "main_builder"),
-        build_gs_bucket = "chromium-fyi-archive",
-    ),
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "chromeos-amd64-generic-rel-goma-rbe-latest",
-    builder_spec = builder_config.copy_from(
-        "ci/chromeos-amd64-generic-rel",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = "goma_latest_client",
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    goma_enable_ats = True,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "ios-device-goma-rbe-latest-clobber",
-    builder_spec = builder_config.copy_from(
-        "ci/ios-device",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                    "clobber",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    cores = None,
-    os = os.MAC_DEFAULT,
-    xcode = xcode.x14main,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "linux-archive-rel-goma-rbe-ats-latest",
-    builder_spec = builder_config.copy_from(
-        "ci/linux-archive-rel",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    goma_enable_ats = True,
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "linux-archive-rel-goma-rbe-latest",
-    builder_spec = builder_config.copy_from(
-        "ci/linux-archive-rel",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-)
-
-fyi_goma_rbe_latest_client_builder(
-    name = "mac-archive-rel-goma-rbe-latest",
-    builder_spec = builder_config.copy_from(
-        "ci/mac-archive-rel",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = [
-                    "goma_latest_client",
-                ],
-            ),
-            build_gs_bucket = "chromium-fyi-archive",
-        ),
-    ),
-    os = os.MAC_DEFAULT,
-    goma_jobs = goma.jobs.J80,
-)
-
 def goma_builder(
         *,
         name,
@@ -662,54 +372,6 @@
 )
 
 goma_builder(
-    name = "Chromium Android ARM 32-bit Goma RBE ToT",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(
-            config = "chromium",
-            apply_configs = ["android"],
-        ),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "goma_failfast",
-                "goma_client_candidate",
-                "clobber",
-            ],
-            target_bits = 32,
-            target_platform = builder_config.target_platform.ANDROID,
-        ),
-        android_config = builder_config.android_config(config = "main_builder_mb"),
-    ),
-    execution_timeout = 5 * time.hour,
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = False,
-)
-
-goma_builder(
-    name = "Chromium Android ARM 32-bit Goma RBE ToT (ATS)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(
-            config = "chromium",
-            apply_configs = ["android"],
-        ),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "goma_failfast",
-                "goma_client_candidate",
-                "clobber",
-            ],
-            target_bits = 32,
-            target_platform = builder_config.target_platform.ANDROID,
-        ),
-        android_config = builder_config.android_config(config = "main_builder_mb"),
-    ),
-    execution_timeout = 5 * time.hour,
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = True,
-)
-
-goma_builder(
     name = "Chromium Linux Goma RBE Staging",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(config = "chromium"),
@@ -726,23 +388,6 @@
 )
 
 goma_builder(
-    name = "Chromium Linux Goma RBE Staging (clobber)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_STAGING,
-)
-
-goma_builder(
     name = "Chromium Linux Goma RBE Staging (dbg)",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(config = "chromium"),
@@ -759,83 +404,12 @@
 )
 
 goma_builder(
-    name = "Chromium Linux Goma RBE Staging (dbg) (clobber)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_STAGING,
-)
-
-goma_builder(
     name = "chromeos-amd64-generic-rel-goma-rbe-staging",
     builder_spec = builder_config.copy_from("ci/chromeos-amd64-generic-rel"),
     goma_backend = goma.backend.RBE_STAGING,
     goma_enable_ats = True,
 )
 
-goma_builder(
-    name = "Chromium Linux Goma RBE ToT",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "goma_client_candidate",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = False,
-)
-
-goma_builder(
-    name = "Chromium Linux Goma RBE ToT (ATS)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "goma_client_candidate",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = True,
-)
-
-goma_builder(
-    name = "chromeos-amd64-generic-rel-goma-rbe-tot",
-    builder_spec = builder_config.copy_from(
-        "ci/chromeos-amd64-generic-rel",
-        lambda spec: structs.evolve(
-            spec,
-            chromium_config = structs.extend(
-                spec.chromium_config,
-                apply_configs = ["goma_client_candidate"],
-            ),
-        ),
-    ),
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = True,
-)
-
 def goma_mac_builder(
         *,
         name,
@@ -849,29 +423,6 @@
     )
 
 goma_mac_builder(
-    name = "Chromium iOS Goma RBE ToT",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "ios"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "mac_toolchain",
-                "goma_failfast",
-                "goma_client_candidate",
-                "clobber",
-            ],
-            build_config = builder_config.build_config.RELEASE,
-            target_bits = 64,
-            target_platform = builder_config.target_platform.IOS,
-        ),
-    ),
-    os = os.MAC_DEFAULT,
-    goma_backend = goma.backend.RBE_TOT,
-    xcode = xcode.x14main,
-)
-
-goma_mac_builder(
     name = "Chromium Mac Goma RBE Staging",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(config = "chromium"),
@@ -888,23 +439,6 @@
 )
 
 goma_mac_builder(
-    name = "Chromium Mac Goma RBE Staging (clobber)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_STAGING,
-)
-
-goma_mac_builder(
     name = "Chromium Mac Goma RBE Staging (dbg)",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(config = "chromium"),
@@ -920,23 +454,6 @@
     goma_backend = goma.backend.RBE_STAGING,
 )
 
-goma_mac_builder(
-    name = "Chromium Mac Goma RBE ToT",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "goma_client_candidate",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_TOT,
-)
-
 def goma_windows_builder(
         *,
         name,
@@ -968,42 +485,6 @@
 )
 
 goma_windows_builder(
-    name = "Chromium Win Goma RBE Staging (clobber)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_STAGING,
-    goma_enable_ats = False,
-)
-
-goma_windows_builder(
-    name = "Chromium Win Goma RBE ToT",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "goma_client_candidate",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = False,
-)
-
-goma_windows_builder(
     name = "Chromium Win Goma RBE ATS Staging",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(config = "chromium"),
@@ -1019,39 +500,3 @@
     goma_backend = goma.backend.RBE_STAGING,
     goma_enable_ats = True,
 )
-
-goma_windows_builder(
-    name = "Chromium Win Goma RBE ATS Staging (clobber)",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "clobber",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_STAGING,
-    goma_enable_ats = True,
-)
-
-goma_windows_builder(
-    name = "Chromium Win Goma RBE ATS ToT",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(config = "chromium"),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-                "goma_failfast",
-                "goma_client_candidate",
-            ],
-            target_bits = 64,
-        ),
-    ),
-    goma_backend = goma.backend.RBE_TOT,
-    goma_enable_ats = True,
-)
diff --git a/infra/config/subprojects/goma/subproject.star b/infra/config/subprojects/goma/subproject.star
index 3c3518a..0db35c4 100644
--- a/infra/config/subprojects/goma/subproject.star
+++ b/infra/config/subprojects/goma/subproject.star
@@ -5,5 +5,4 @@
 exec("./goma.star")
 exec("./consoles/chromium.goma.star")
 exec("./consoles/chromium.goma.fyi.star")
-exec("./consoles/goma.latest.star")
 exec("./consoles/luci.chromium.goma.star")
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index 6eb0c181..40fc3a6 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -249,6 +249,12 @@
       <message name="IDS_IOS_DEFAULT_PAGE_MODE_DESKTOP_SUBTITLE" desc="The subtitle explaining what the choosen mode (here desktop, the other possible state is mobile) will do in the browser">
         This means Chromium will request the desktop site every time.
       </message>
+      <message name="IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT" desc="Chromium version of the title for the default browser half screen promo. [iOS only]">
+        Open Chromium From Any App
+      </message>
+      <message name="IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT" desc="Chromium version of the subtitle for the default browser half screen promo. [iOS only]">
+        Automatically use Chromium when you tap links in messages, documents, and other apps.
+      </message>
       <message name="IDS_IOS_DEFAULT_PAGE_MODE_MOBILE_SUBTITLE" desc="The subtitle explaining what the choosen mode (here mobile, the other possible state is desktop) will do in the browser">
         This means Chromium will request the mobile site every time.
       </message>
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT.png.sha1
new file mode 100644
index 0000000..72bac181
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT.png.sha1
@@ -0,0 +1 @@
+030900be1e0cf250b1a4a2735b44c99f7fa1f3f8
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT.png.sha1
new file mode 100644
index 0000000..72bac181
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT.png.sha1
@@ -0,0 +1 @@
+030900be1e0cf250b1a4a2735b44c99f7fa1f3f8
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index 5c395283..d01ca9b8 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -249,6 +249,12 @@
       <message name="IDS_IOS_DEFAULT_PAGE_MODE_DESKTOP_SUBTITLE" desc="The subtitle explaining what the choosen mode (here desktop, the other possible state is mobile) will do in the browser">
         This means Chrome will request the desktop site every time.
       </message>
+      <message name="IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT" desc="Title for the default browser video promo. [iOS only]">
+        Open Chrome From Any App
+      </message>
+      <message name="IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT" desc="Subtitle for the default browser video half screen promo. [iOS only]">
+        Automatically use Chrome when you tap links in messages, documents, and other apps.
+      </message>
       <message name="IDS_IOS_DEFAULT_PAGE_MODE_MOBILE_SUBTITLE" desc="The subtitle explaining what the choosen mode (here mobile, the other possible state is desktop) will do in the browser">
         This means Chrome will request the mobile site every time.
       </message>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT.png.sha1
new file mode 100644
index 0000000..72bac181
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT.png.sha1
@@ -0,0 +1 @@
+030900be1e0cf250b1a4a2735b44c99f7fa1f3f8
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT.png.sha1
new file mode 100644
index 0000000..72bac181
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT.png.sha1
@@ -0,0 +1 @@
+030900be1e0cf250b1a4a2735b44c99f7fa1f3f8
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 3ef17ad..f95737aa 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -843,6 +843,12 @@
       <message name="IDS_IOS_DEFAULT_NON_MODAL_PRIMARY_BUTTON_TEXT" desc="Text of button that will take users to Chrome or iOS settings where they can change the default browser. Used in the non-modal default browser promo [iOS only]">
         Set…
       </message>
+      <message name="IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_PRIMARY_BUTTON_TEXT" desc="Text of button that will take users to the video default promo [iOS only]">
+        Show Me How
+      </message>
+      <message name="IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SECONDARY_BUTTON_TEXT" desc="Text of button that will dismiss the half screen default browser promo [iOS only]">
+        No, Thanks
+      </message>
       <message name="IDS_IOS_DEFAULT_BROWSER_REMIND_ME_LATER_BUTTON_TEXT" desc="Text of button that will show the default browser modal promo UI again at a later time.  [iOS only]">
         Remind Me Later
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_PRIMARY_BUTTON_TEXT.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_PRIMARY_BUTTON_TEXT.png.sha1
new file mode 100644
index 0000000..72bac181
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_PRIMARY_BUTTON_TEXT.png.sha1
@@ -0,0 +1 @@
+030900be1e0cf250b1a4a2735b44c99f7fa1f3f8
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SECONDARY_BUTTON_TEXT.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SECONDARY_BUTTON_TEXT.png.sha1
new file mode 100644
index 0000000..72bac181
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SECONDARY_BUTTON_TEXT.png.sha1
@@ -0,0 +1 @@
+030900be1e0cf250b1a4a2735b44c99f7fa1f3f8
\ No newline at end of file
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 354731a..4108b903 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1104,9 +1104,9 @@
      flag_descriptions::kOmniboxOnDeviceTailSuggestionsDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(omnibox::kOnDeviceTailModel)},
     {"enable-browser-lockdown-mode",
-     flag_descriptions::kEnableBrowserLockdownModeName,
-     flag_descriptions::kEnableBrowserLockdownModeDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(web::kEnableBrowserLockdownMode)},
+     flag_descriptions::kBrowserLockdownModeAvailableName,
+     flag_descriptions::kBrowserLockdownModeAvailableDescription,
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(web::kBrowserLockdownModeAvailable)},
     {"enable-button-configuration-usage",
      flag_descriptions::kEnableUIButtonConfigurationName,
      flag_descriptions::kEnableUIButtonConfigurationDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 2f517f8..f906175 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -283,8 +283,8 @@
 const char kEnableBookmarksAccountStorageDescription[] =
     "Enable bookmarks account storage and related UI features.";
 
-const char kEnableBrowserLockdownModeName[] = "Enable Browser Lockdown Mode";
-const char kEnableBrowserLockdownModeDescription[] =
+const char kBrowserLockdownModeAvailableName[] = "Enable Browser Lockdown Mode";
+const char kBrowserLockdownModeAvailableDescription[] =
     "Enable browser lockdown mode.";
 
 const char kEnableCBDSignOutName[] = "Enable Clear Browsing Data Sign-out";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index d29426a..ffac8b7 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -217,8 +217,8 @@
 extern const char kEnableBookmarksAccountStorageDescription[];
 
 // Title and description for the flag to enable browser lockdown mode.
-extern const char kEnableBrowserLockdownModeName[];
-extern const char kEnableBrowserLockdownModeDescription[];
+extern const char kBrowserLockdownModeAvailableName[];
+extern const char kBrowserLockdownModeAvailableDescription[];
 
 // Title and description for the flag to enable checking feed visibility on
 // attention log start.
diff --git a/ios/chrome/browser/link_to_text/link_to_text_java_script_feature.h b/ios/chrome/browser/link_to_text/link_to_text_java_script_feature.h
index 9f2474ad..6f9122af 100644
--- a/ios/chrome/browser/link_to_text/link_to_text_java_script_feature.h
+++ b/ios/chrome/browser/link_to_text/link_to_text_java_script_feature.h
@@ -5,6 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_LINK_TO_TEXT_LINK_TO_TEXT_JAVA_SCRIPT_FEATURE_H_
 #define IOS_CHROME_BROWSER_LINK_TO_TEXT_LINK_TO_TEXT_JAVA_SCRIPT_FEATURE_H_
 
+#include "base/gtest_prod_util.h"
 #import "base/memory/weak_ptr.h"
 #import "base/no_destructor.h"
 #import "base/values.h"
diff --git a/ios/chrome/browser/shared/ui/util/uikit_ui_util.h b/ios/chrome/browser/shared/ui/util/uikit_ui_util.h
index 875dcb656..2a4d251d 100644
--- a/ios/chrome/browser/shared/ui/util/uikit_ui_util.h
+++ b/ios/chrome/browser/shared/ui/util/uikit_ui_util.h
@@ -159,6 +159,10 @@
 
 // Returns the current first responder for keyWindow.
 UIResponder* GetFirstResponder();
+// Returns the current first responder for `windowScene.keyWindow`. If none,
+// returns the current first responder for the first window which has one in
+// this scene, if any.
+UIResponder* GetFirstResponderInWindowScene(UIWindowScene* windowScene);
 
 // Trigger a haptic vibration for various types of actions. This is a no-op for
 // devices that do not support haptic feedback.
diff --git a/ios/chrome/browser/shared/ui/util/uikit_ui_util.mm b/ios/chrome/browser/shared/ui/util/uikit_ui_util.mm
index a7dd86f..796b8f73 100644
--- a/ios/chrome/browser/shared/ui/util/uikit_ui_util.mm
+++ b/ios/chrome/browser/shared/ui/util/uikit_ui_util.mm
@@ -288,6 +288,31 @@
   return GetFirstResponderSubview(GetAnyKeyWindow());
 }
 
+UIResponder* GetFirstResponderInWindowScene(UIWindowScene* windowScene) {
+  DCHECK(NSThread.isMainThread);
+  if (!windowScene) {
+    return nil;
+  }
+
+  // First checking the key window for this window scene.
+  UIResponder* responder = GetFirstResponderSubview(windowScene.keyWindow);
+  if (responder) {
+    return responder;
+  }
+
+  for (UIWindow* window in windowScene.windows) {
+    if (window.isKeyWindow) {
+      continue;
+    }
+    responder = GetFirstResponderSubview(window);
+    if (responder) {
+      return responder;
+    }
+  }
+
+  return nil;
+}
+
 // Trigger a haptic vibration for the user selecting an action. This is a no-op
 // for devices that do not support it.
 void TriggerHapticFeedbackForImpact(UIImpactFeedbackStyle impactStyle) {
diff --git a/ios/chrome/browser/signin/authentication_service.h b/ios/chrome/browser/signin/authentication_service.h
index 5709511..4b2203b 100644
--- a/ios/chrome/browser/signin/authentication_service.h
+++ b/ios/chrome/browser/signin/authentication_service.h
@@ -133,13 +133,6 @@
   virtual void SignIn(id<SystemIdentity> identity,
                       signin_metrics::AccessPoint access_point);
 
-  // Grants signin::ConsentLevel::kSignin to `identity` and records the signin
-  // at `ACCESS_POINT_UNKNOWN`. This method is used for testing. Virtual for
-  // testing.
-  // TODO(crbug.com/1261772): Remove this method and update all unit
-  // tests accordingly.
-  virtual void SignIn(id<SystemIdentity> identity);
-
   // Grants signin::ConsentLevel::kSync to `identity` and records the event at
   // `access_point`. This starts setting up Sync-the-feature, but the setup will
   // only complete once SyncUserSettings::SetFirstSetupComplete() is called.
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index ab45d8c..d6d8310 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -383,10 +383,6 @@
   crash_keys::SetCurrentlySignedIn(true);
 }
 
-void AuthenticationService::SignIn(id<SystemIdentity> identity) {
-  SignIn(identity, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
-}
-
 void AuthenticationService::GrantSyncConsent(
     id<SystemIdentity> identity,
     signin_metrics::AccessPoint access_point) {
diff --git a/ios/chrome/browser/signin/authentication_service_unittest.mm b/ios/chrome/browser/signin/authentication_service_unittest.mm
index b32da35..9d3702f4 100644
--- a/ios/chrome/browser/signin/authentication_service_unittest.mm
+++ b/ios/chrome/browser/signin/authentication_service_unittest.mm
@@ -293,7 +293,8 @@
 TEST_F(AuthenticationServiceTest, TestHandleForgottenIdentityNoPromptSignIn) {
   // Sign in.
   SetExpectationsForSignInAndSync();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   authentication_service()->GrantSyncConsent(
       identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
@@ -321,7 +322,8 @@
 TEST_F(AuthenticationServiceTest, TestHandleForgottenIdentityPromptSignIn) {
   // Sign in.
   SetExpectationsForSignInAndSync();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   authentication_service()->GrantSyncConsent(
       identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
@@ -348,7 +350,8 @@
        TestHandleForgottenIdentityNoPromptSignInAndSync) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   // Set the authentication service as "In Background", remove identity and run
   // the loop.
@@ -372,7 +375,8 @@
        OnApplicationEnterForegroundReloadCredentials) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   fake_system_identity_manager()->AddIdentities(@[ @"foo3" ]);
 
@@ -405,7 +409,8 @@
 // Tests the account list is approved after adding an account with in Chrome.
 TEST_F(AuthenticationServiceTest, AccountListApprovedByUser_AddedByUser) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   fake_system_identity_manager()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged(/*notify_user=*/false);
@@ -417,7 +422,8 @@
 // app (through the keychain).
 TEST_F(AuthenticationServiceTest, AccountListApprovedByUser_ChangedByKeychain) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   fake_system_identity_manager()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged(/*notify_user=*/true);
@@ -430,7 +436,8 @@
 TEST_F(AuthenticationServiceTest,
        AccountListApprovedByUser_ChangedTwiceByKeychain) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   fake_system_identity_manager()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged(/*notify_user=*/true);
@@ -449,7 +456,8 @@
 TEST_F(AuthenticationServiceTest,
        AccountListApprovedByUser_ResetOntwoBackgrounds) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   fake_system_identity_manager()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged(/*notify_user=*/true);
@@ -475,7 +483,8 @@
 TEST_F(AuthenticationServiceTest, HasPrimaryIdentityBackground) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_TRUE(authentication_service()->HasPrimaryIdentity(
       signin::ConsentLevel::kSignin));
 
@@ -493,7 +502,8 @@
 // notifications that the state of error has changed.
 TEST_F(AuthenticationServiceTest, MDMErrorsClearedOnForeground) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 2UL);
 
   SetCachedMDMInfo(identity(0), CreateRefreshAccessTokenError(identity(0)));
@@ -533,7 +543,8 @@
 // Tests that MDM errors are correctly cleared when signing out.
 TEST_F(AuthenticationServiceTest, MDMErrorsClearedOnSignout) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 2UL);
 
   SetCachedMDMInfo(identity(0), CreateRefreshAccessTokenError(identity(0)));
@@ -550,7 +561,8 @@
 TEST_F(AuthenticationServiceTest,
        MDMErrorsClearedOnSignoutAndClearBrowsingData) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 2UL);
 
   SetCachedMDMInfo(identity(0), CreateRefreshAccessTokenError(identity(0)));
@@ -568,7 +580,8 @@
   fake_system_identity_manager()->AddManagedIdentities(@[ @"foo3" ]);
 
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(2));
+  authentication_service()->SignIn(
+      identity(2), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3UL);
   EXPECT_TRUE(authentication_service()->HasPrimaryIdentityManaged(
       signin::ConsentLevel::kSignin));
@@ -588,7 +601,8 @@
   fake_system_identity_manager()->AddManagedIdentities(@[ @"foo3" ]);
 
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(2));
+  authentication_service()->SignIn(
+      identity(2), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3UL);
   EXPECT_TRUE(authentication_service()->HasPrimaryIdentityManaged(
       signin::ConsentLevel::kSignin));
@@ -610,7 +624,8 @@
   fake_system_identity_manager()->AddManagedIdentities(@[ @"foo3" ]);
 
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(2));
+  authentication_service()->SignIn(
+      identity(2), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3UL);
   EXPECT_TRUE(authentication_service()->HasPrimaryIdentityManaged(
       signin::ConsentLevel::kSignin));
@@ -628,7 +643,8 @@
 // to MDM service when necessary.
 TEST_F(AuthenticationServiceTest, HandleMDMNotification) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
   signin::UpdatePersistentErrorOfRefreshTokenForAccount(
@@ -665,7 +681,8 @@
 // the primary account is blocked.
 TEST_F(AuthenticationServiceTest, HandleMDMBlockedNotification) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
   signin::UpdatePersistentErrorOfRefreshTokenForAccount(
@@ -712,7 +729,8 @@
 // Tests that MDM dialog is shown when there is a cached error and a
 // corresponding error for the account.
 TEST_F(AuthenticationServiceTest, ShowMDMErrorDialog) {
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
   signin::UpdatePersistentErrorOfRefreshTokenForAccount(
@@ -731,7 +749,8 @@
 TEST_F(AuthenticationServiceTest, SigninAndSyncDecoupled) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   EXPECT_NSEQ(identity(0), authentication_service()->GetPrimaryIdentity(
                                signin::ConsentLevel::kSignin));
@@ -763,7 +782,8 @@
   browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
 
   // Attempt to sign in, and verify there is a crash.
-  EXPECT_CHECK_DEATH(authentication_service()->SignIn(identity(0)));
+  EXPECT_CHECK_DEATH(authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN));
 }
 
 // Tests that reauth prompt is not set if the primary identity is restricted and
@@ -773,7 +793,8 @@
   authentication_service()->AddObserver(&observer_test);
   // Sign in.
   SetExpectationsForSignInAndSync();
-  authentication_service()->SignIn(identity(0));
+  authentication_service()->SignIn(
+      identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   authentication_service()->GrantSyncConsent(
       identity(0), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
diff --git a/ios/chrome/browser/text_selection/text_classifier_model_service.h b/ios/chrome/browser/text_selection/text_classifier_model_service.h
index 5a76585..4bdade2 100644
--- a/ios/chrome/browser/text_selection/text_classifier_model_service.h
+++ b/ios/chrome/browser/text_selection/text_classifier_model_service.h
@@ -5,6 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_TEXT_SELECTION_TEXT_CLASSIFIER_MODEL_SERVICE_H_
 #define IOS_CHROME_BROWSER_TEXT_SELECTION_TEXT_CLASSIFIER_MODEL_SERVICE_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/sequence_checker.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/optimization_guide/core/optimization_target_model_observer.h"
diff --git a/ios/chrome/browser/translate/chrome_ios_translate_client.h b/ios/chrome/browser/translate/chrome_ios_translate_client.h
index 176a419a..8b030b8a 100644
--- a/ios/chrome/browser/translate/chrome_ios_translate_client.h
+++ b/ios/chrome/browser/translate/chrome_ios_translate_client.h
@@ -10,6 +10,7 @@
 #include <memory>
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "components/translate/core/browser/translate_client.h"
 #include "components/translate/core/browser/translate_step.h"
 #include "components/translate/core/common/translate_errors.h"
diff --git a/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm b/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm
index 3c76769e..9ec513fc 100644
--- a/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm
@@ -8,6 +8,7 @@
 
 #import "base/functional/bind.h"
 #import "base/memory/ptr_util.h"
+#import "components/signin/public/base/signin_metrics.h"
 #import "components/sync_preferences/testing_pref_service_syncable.h"
 #import "ios/chrome/browser/infobars/infobar_ios.h"
 #import "ios/chrome/browser/infobars/infobar_utils.h"
@@ -63,7 +64,9 @@
     AuthenticationService* authentication_service =
         AuthenticationServiceFactory::GetForBrowserState(
             chrome_browser_state_.get());
-    authentication_service->SignIn(chrome_identity);
+    authentication_service->SignIn(
+        chrome_identity,
+        signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR);
   }
 
   web::WebTaskEnvironment task_environment_;
diff --git a/ios/chrome/browser/ui/authentication/signed_in_accounts/signed_in_accounts_view_controller_unittest.mm b/ios/chrome/browser/ui/authentication/signed_in_accounts/signed_in_accounts_view_controller_unittest.mm
index e73d0b9..d5fb391d 100644
--- a/ios/chrome/browser/ui/authentication/signed_in_accounts/signed_in_accounts_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signed_in_accounts/signed_in_accounts_view_controller_unittest.mm
@@ -62,7 +62,8 @@
             browser_state_.get());
     AuthenticationService* auth_service =
         AuthenticationServiceFactory::GetForBrowserState(browser_state_.get());
-    auth_service->SignIn(account_manager_service->GetDefaultIdentity());
+    auth_service->SignIn(account_manager_service->GetDefaultIdentity(),
+                         signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   }
 
  protected:
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_mediator_unittest.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_mediator_unittest.mm
index a018f55..286afa5 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_mediator_unittest.mm
@@ -9,6 +9,7 @@
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
 #import "base/test/metrics/histogram_tester.h"
+#import "components/signin/public/base/signin_metrics.h"
 #import "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
 #import "components/sync_preferences/testing_pref_service_syncable.h"
@@ -108,7 +109,9 @@
   // Signs in and simulates cookies being added on the web.
   void SigninAndSimulateCookies(ConsistencyPromoSigninMediator* mediator,
                                 id<SystemIdentity> identity) {
-    GetAuthenticationService()->SignIn(identity);
+    GetAuthenticationService()->SignIn(
+        identity,
+        signin_metrics::AccessPoint::ACCESS_POINT_ACCOUNT_CONSISTENCY_SERVICE);
     OCMExpect([mediator_delegate_mock_
         consistencyPromoSigninMediatorSignInDone:mediator
                                     withIdentity:identity]);
@@ -129,7 +132,9 @@
   // Signs in and simulates a cookie error.
   void SigninAndSimulateError(ConsistencyPromoSigninMediator* mediator,
                               id<SystemIdentity> identity) {
-    GetAuthenticationService()->SignIn(identity);
+    GetAuthenticationService()->SignIn(
+        identity,
+        signin_metrics::AccessPoint::ACCESS_POINT_ACCOUNT_CONSISTENCY_SERVICE);
     __block BOOL error_did_happen_called = NO;
     OCMExpect(
         [mediator_delegate_mock_
diff --git a/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm
index f30d992..0183cdf 100644
--- a/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm
@@ -118,6 +118,7 @@
                                                ACCESS_POINT_FORCED_SIGNIN
                                promoAction:signin_metrics::PromoAction::
                                                PROMO_ACTION_NO_SIGNIN_PROMO];
+    case kHistorySync:
     case kTangibleSync:
     case kDefaultBrowserPromo:
     case kStepsCompleted:
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm b/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm
index 5eac9f9..eac6691 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm
@@ -12,6 +12,7 @@
 #import "base/test/scoped_feature_list.h"
 #import "base/version.h"
 #import "components/pref_registry/pref_registry_syncable.h"
+#import "components/signin/public/base/signin_metrics.h"
 #import "components/signin/public/base/signin_pref_names.h"
 #import "components/signin/public/base/signin_switches.h"
 #import "components/sync/base/pref_names.h"
@@ -286,7 +287,8 @@
   AuthenticationService* authentication_service =
       AuthenticationServiceFactory::GetForBrowserState(
           chrome_browser_state_.get());
-  authentication_service->SignIn(identity);
+  authentication_service->SignIn(
+      identity, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   IdentitySigninState state =
       signin::GetPrimaryIdentitySigninState(chrome_browser_state_.get());
@@ -305,7 +307,8 @@
   AuthenticationService* authentication_service =
       AuthenticationServiceFactory::GetForBrowserState(
           chrome_browser_state_.get());
-  authentication_service->SignIn(identity);
+  authentication_service->SignIn(
+      identity, signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO);
   authentication_service->GrantSyncConsent(
       identity, signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO);
   chrome_browser_state_->GetPrefs()->SetBoolean(
@@ -332,7 +335,8 @@
   AuthenticationService* authentication_service =
       AuthenticationServiceFactory::GetForBrowserState(
           chrome_browser_state_.get());
-  authentication_service->SignIn(identity);
+  authentication_service->SignIn(
+      identity, signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO);
   authentication_service->GrantSyncConsent(
       identity, signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO);
 
diff --git a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm
index 2de22271..aec8656 100644
--- a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm
@@ -54,7 +54,8 @@
     system_identity_manager->AddIdentity(identity);
     AuthenticationService* authentication_service =
         AuthenticationServiceFactory::GetForBrowserState(browser_state_.get());
-    authentication_service->SignIn(identity);
+    authentication_service->SignIn(
+        identity, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
     browser_ = std::make_unique<TestBrowser>(browser_state_.get());
   }
diff --git a/ios/chrome/browser/ui/authentication/signin/two_screens_signin/BUILD.gn b/ios/chrome/browser/ui/authentication/signin/two_screens_signin/BUILD.gn
index e93c7f0f..a875f33 100644
--- a/ios/chrome/browser/ui/authentication/signin/two_screens_signin/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/signin/two_screens_signin/BUILD.gn
@@ -18,6 +18,7 @@
     "//ios/chrome/browser/ui/authentication/signin:signin_screen_provider",
     "//ios/chrome/browser/ui/first_run:screen_delegate",
     "//ios/chrome/browser/ui/first_run:utils",
+    "//ios/chrome/browser/ui/first_run/history_sync",
     "//ios/chrome/browser/ui/first_run/signin",
     "//ios/chrome/browser/ui/first_run/tangible_sync",
     "//ios/chrome/browser/ui/screen:screen_provider",
diff --git a/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator.mm
index c1a2298d4..b5af6cc 100644
--- a/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator.mm
@@ -16,6 +16,7 @@
 #import "ios/chrome/browser/ui/authentication/signin/signin_coordinator+protected.h"
 #import "ios/chrome/browser/ui/authentication/signin/signin_sync_screen_provider.h"
 #import "ios/chrome/browser/ui/first_run/first_run_util.h"
+#import "ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/tangible_sync/tangible_sync_screen_coordinator.h"
 #import "ios/chrome/browser/ui/screen/screen_provider.h"
@@ -141,6 +142,12 @@
                                    browser:self.browser
                                   firstRun:NO
                                   delegate:self];
+    case kHistorySync:
+      return [[HistorySyncScreenCoordinator alloc]
+          initWithBaseNavigationController:_navigationController
+                                   browser:self.browser
+                                  firstRun:NO
+                                  delegate:self];
     case kDefaultBrowserPromo:
     case kStepsCompleted:
       break;
diff --git a/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator_unittest.mm b/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator_unittest.mm
index f8c41e2..58743b5 100644
--- a/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/two_screens_signin/two_screens_signin_coordinator_unittest.mm
@@ -87,7 +87,8 @@
     AuthenticationService* auth_service = static_cast<AuthenticationService*>(
         AuthenticationServiceFactory::GetInstance()->GetForBrowserState(
             browser_state_.get()));
-    auth_service->SignIn(fake_identity);
+    auth_service->SignIn(fake_identity,
+                         signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   }
 
   // Advances the coordinator to the next screen.
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_mediator_unittest.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_mediator_unittest.mm
index 0304491..b4c4f96 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_mediator_unittest.mm
@@ -133,7 +133,8 @@
               toBrowserState:browser_state_.get()])
         .andDo(^(NSInvocation* invocation) {
           NSLog(@" signInIdentity ");
-          authentication_service()->SignIn(identity_);
+          authentication_service()->SignIn(
+              identity_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
         });
     if (postSignInAction == PostSignInAction::kCommitSync) {
       OCMExpect(
@@ -520,7 +521,8 @@
   // Signs in with identity 2.
   id<SystemIdentity> identity2 = [FakeSystemIdentity fakeIdentity2];
   fake_system_identity_manager()->AddIdentity(identity2);
-  authentication_service()->SignIn(identity2);
+  authentication_service()->SignIn(
+      identity2, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   // Opens the settings link with identity 1.
   CreateAuthenticationFlow(PostSignInAction::kNone);
@@ -572,7 +574,8 @@
                                      gaiaID:@"foo2ID"
                                        name:@"Fake Foo 2"];
   fake_system_identity_manager()->AddIdentity(identity2);
-  authentication_service()->SignIn(identity2);
+  authentication_service()->SignIn(
+      identity2, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
 
   // Opens the settings link with identity 1.
   CreateAuthenticationFlow(PostSignInAction::kNone);
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm
index f1a95eae..08782ba 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm
@@ -579,7 +579,8 @@
   AddDefaultIdentity();
   identity_ = [FakeSystemIdentity fakeIdentity2];
   fake_system_identity_manager()->AddIdentity(identity_);
-  GetAuthenticationService()->SignIn(identity_);
+  GetAuthenticationService()->SignIn(
+      identity_, signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO);
   CreateMediator(signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS);
   ExpectConfiguratorNotification(NO /* identity changed */);
   [mediator_ signinPromoViewIsVisible];
diff --git a/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator_unittest.mm b/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator_unittest.mm
index 1f0574a..09c63c0 100644
--- a/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator_unittest.mm
@@ -9,6 +9,7 @@
 #import "base/mac/foundation_util.h"
 #import "base/test/task_environment.h"
 #import "components/prefs/pref_service.h"
+#import "components/signin/public/base/signin_metrics.h"
 #import "components/sync/test/mock_sync_service.h"
 #import "ios/chrome/browser/policy/policy_util.h"
 #import "ios/chrome/browser/prefs/pref_names.h"
@@ -111,7 +112,8 @@
 // Tests that a signed-in user with Sync enabled will have an action sheet with
 // a sign-out title.
 TEST_F(SignoutActionSheetCoordinatorTest, SignedInUserWithSync) {
-  authentication_service()->SignIn(identity_);
+  authentication_service()->SignIn(
+      identity_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   ON_CALL(*sync_setup_service_mock_, IsFirstSetupComplete())
       .WillByDefault(testing::Return(true));
 
@@ -124,7 +126,8 @@
 // Tests that a signed-in user with Sync disabled will have an action sheet with
 // no title.
 TEST_F(SignoutActionSheetCoordinatorTest, SignedInUserWithoutSync) {
-  authentication_service()->SignIn(identity_);
+  authentication_service()->SignIn(
+      identity_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   ON_CALL(*sync_setup_service_mock_, IsFirstSetupComplete())
       .WillByDefault(testing::Return(false));
 
@@ -141,7 +144,8 @@
   GetLocalState()->SetInteger(prefs::kBrowserSigninPolicy,
                               static_cast<int>(BrowserSigninMode::kForced));
 
-  authentication_service()->SignIn(identity_);
+  authentication_service()->SignIn(
+      identity_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   ON_CALL(*sync_setup_service_mock_, IsFirstSetupComplete())
       .WillByDefault(testing::Return(false));
 
@@ -155,7 +159,8 @@
 // Tests that a signed-in managed user with Sync enabled will have an action
 // sheet with a sign-out title.
 TEST_F(SignoutActionSheetCoordinatorTest, SignedInManagedUserWithSync) {
-  authentication_service()->SignIn(identity_);
+  authentication_service()->SignIn(
+      identity_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   ON_CALL(*sync_setup_service_mock_, IsFirstSetupComplete())
       .WillByDefault(testing::Return(true));
 
@@ -168,7 +173,8 @@
 // Tests that a signed-in managed user with Sync disabled will have an action
 // sheet with no title.
 TEST_F(SignoutActionSheetCoordinatorTest, SignedInManagedUserWithoutSync) {
-  authentication_service()->SignIn(identity_);
+  authentication_service()->SignIn(
+      identity_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   ON_CALL(*sync_setup_service_mock_, IsFirstSetupComplete())
       .WillByDefault(testing::Return(false));
 
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_mediator_unittest.mm b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_mediator_unittest.mm
index 85a0efb2b..a02e03ac 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_mediator_unittest.mm
@@ -153,7 +153,8 @@
   AddIdentities();
   CreateMediator();
 
-  GetAuthenticationService()->SignIn(identity2_);
+  GetAuthenticationService()->SignIn(
+      identity2_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   [mediator_ start];
 
   ASSERT_NSEQ(identity2_, mediator_.selectedIdentity);
@@ -166,7 +167,8 @@
   AddIdentities();
   CreateMediator();
 
-  GetAuthenticationService()->SignIn(identity3_);
+  GetAuthenticationService()->SignIn(
+      identity3_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   [mediator_ start];
   ASSERT_NSEQ(identity3_, mediator_.selectedIdentity);
   GetAuthenticationService()->SignOut(signin_metrics::ProfileSignout::kTest,
@@ -200,7 +202,8 @@
   AddIdentities();
   CreateMediator();
 
-  GetAuthenticationService()->SignIn(identity1_);
+  GetAuthenticationService()->SignIn(
+      identity1_, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
   mediator_.selectedIdentity = identity2_;
   [mediator_ start];
 
diff --git a/ios/chrome/browser/ui/bring_android_tabs/bring_android_tabs_prompt_bottom_message_view.swift b/ios/chrome/browser/ui/bring_android_tabs/bring_android_tabs_prompt_bottom_message_view.swift
index bbb01220..0c91489 100644
--- a/ios/chrome/browser/ui/bring_android_tabs/bring_android_tabs_prompt_bottom_message_view.swift
+++ b/ios/chrome/browser/ui/bring_android_tabs/bring_android_tabs_prompt_bottom_message_view.swift
@@ -5,13 +5,14 @@
 import SwiftUI
 import ios_chrome_common_ui_colors_swift
 
-private let kPromptInnerPadding: CGFloat = 7
+private let kPromptInnerPaddingHorizontal: CGFloat = 12
+private let kPromptInnerPaddingVertical: CGFloat = 8
 private let kPromptOuterPaddingHorizontal: CGFloat = 6
 private let kPromptOuterPaddingVertical: CGFloat = 8
 private let kPromptCornerRadius: CGFloat = 20
-private let kIconPadding: CGFloat = 12
 private let kTextVerticalSpacing: CGFloat = 12
 private let kTextMinimumScaleFactor: CGFloat = 0.5
+private let kSeparatorHeight: CGFloat = 1
 
 // The view object for "Bring Android Tabs" bottom message prompt.
 struct BringAndroidTabsPromptBottomMessageView: View {
@@ -24,12 +25,12 @@
     HStack(alignment: .top) {
       Image(systemName: kTabsSymbol)
         .imageScale(.large)
-        .padding(kIconPadding - kPromptInnerPadding)
+        .padding(.top, kTextVerticalSpacing)
       VStack(alignment: .leading, spacing: kTextVerticalSpacing) {
         HStack(alignment: .top) {
           VStack(
             alignment: .leading,
-            spacing: kTextVerticalSpacing - kPromptInnerPadding
+            spacing: kTextVerticalSpacing - kPromptInnerPaddingVertical
           ) {
             Text(
               L10nUtils.pluralString(
@@ -47,7 +48,7 @@
             .foregroundColor(.textSecondary)
             .minimumScaleFactor(kTextMinimumScaleFactor)
           }
-          .padding(.top, kTextVerticalSpacing - kPromptInnerPadding)
+          .padding(.top, kTextVerticalSpacing - kPromptInnerPaddingVertical)
           Spacer()
           Button(action: self.close) {
             Image(systemName: kXMarkCircleFillSymbol)
@@ -57,7 +58,7 @@
             kBringAndroidTabsPromptBottomMessageCloseButtonAXId)
         }
         .padding(0)
-        Divider().overlay(Color.separator)
+        Divider().overlay(Color.separator).frame(height: kSeparatorHeight)
         Button(action: self.review) {
           HStack {
             Text(
@@ -78,7 +79,8 @@
       }
       .padding(.bottom, kTextVerticalSpacing)
     }
-    .padding(kPromptInnerPadding)
+    .padding(.horizontal, kPromptInnerPaddingHorizontal)
+    .padding(.vertical, kPromptInnerPaddingVertical)
     .background(Color.primaryBackground)
     .clipShape(RoundedRectangle(cornerRadius: kPromptCornerRadius))
     .environment(\.colorScheme, .dark)
diff --git a/ios/chrome/browser/ui/default_promo/BUILD.gn b/ios/chrome/browser/ui/default_promo/BUILD.gn
index e8aac25..2b8f7e8e 100644
--- a/ios/chrome/browser/ui/default_promo/BUILD.gn
+++ b/ios/chrome/browser/ui/default_promo/BUILD.gn
@@ -16,6 +16,11 @@
     "default_browser_string_util.h",
     "default_browser_string_util.mm",
     "default_promo_non_modal_presentation_delegate.h",
+    "half_screen_promo_coordinator.h",
+    "half_screen_promo_coordinator.mm",
+    "half_screen_promo_coordinator_delegate.h",
+    "half_screen_promo_view_controller.h",
+    "half_screen_promo_view_controller.mm",
     "tailored_promo_coordinator.h",
     "tailored_promo_coordinator.mm",
     "tailored_promo_util.h",
diff --git a/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.h b/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.h
new file mode 100644
index 0000000..461bd74
--- /dev/null
+++ b/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.h
@@ -0,0 +1,27 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_COORDINATOR_H_
+
+#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
+
+@protocol HalfScreenPromoCoordinatorDelegate;
+
+// Coordinator to present the half screen promo
+@interface HalfScreenPromoCoordinator : ChromeCoordinator
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser NS_UNAVAILABLE;
+
+@property(nonatomic, weak) id<HalfScreenPromoCoordinatorDelegate> delegate;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.mm b/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.mm
new file mode 100644
index 0000000..7c10d04
--- /dev/null
+++ b/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.mm
@@ -0,0 +1,77 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.h"
+
+#import "ios/chrome/browser/shared/model/browser/browser.h"
+#import "ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator_delegate.h"
+#import "ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.h"
+#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface HalfScreenPromoCoordinator () <
+    UIAdaptivePresentationControllerDelegate,
+    UINavigationControllerDelegate,
+    ConfirmationAlertActionHandler>
+
+// The view controller.
+@property(nonatomic, strong) HalfScreenPromoViewController* viewController;
+
+@end
+
+@implementation HalfScreenPromoCoordinator
+
+@synthesize baseNavigationController = _baseNavigationController;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser {
+  self = [super initWithBaseViewController:navigationController
+                                   browser:browser];
+  if (self) {
+    _baseNavigationController = navigationController;
+  }
+  return self;
+}
+
+#pragma mark - ChromeCoordinator
+
+- (void)start {
+  self.viewController = [[HalfScreenPromoViewController alloc] init];
+  self.viewController.actionHandler = self;
+  [self.baseNavigationController pushViewController:self.viewController
+                                           animated:YES];
+  [super start];
+}
+
+- (void)stop {
+  if (self.baseNavigationController.topViewController == self.viewController) {
+    [self.baseNavigationController popViewControllerAnimated:NO];
+    self.viewController = nil;
+  }
+
+  [super stop];
+}
+
+#pragma mark - ConfirmationAlertActionHandler
+
+- (void)confirmationAlertPrimaryAction {
+  [self.delegate handlePrimaryActionForHalfScreenPromoCoordinator:self];
+}
+
+- (void)confirmationAlertSecondaryAction {
+  [self.delegate handleSecondaryActionForHalfScreenPromoCoordinator:self];
+}
+
+#pragma mark - UIAdaptivePresentationControllerDelegate
+
+- (void)presentationControllerDidDismiss:
+    (UIPresentationController*)presentationController {
+  [self.delegate handleDismissActionForHalfScreenPromoCoordinator:self];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator_delegate.h b/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator_delegate.h
new file mode 100644
index 0000000..a0cc564
--- /dev/null
+++ b/ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator_delegate.h
@@ -0,0 +1,28 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_COORDINATOR_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_COORDINATOR_DELEGATE_H_
+
+#import "ios/chrome/browser/ui/default_promo/half_screen_promo_coordinator.h"
+
+// Delegate protocol to handle communication from HalfScreenPromoCoordinator to
+// the parent coordinator.
+@protocol HalfScreenPromoCoordinatorDelegate
+
+// Invoked when the user clicks on the primary button.
+- (void)handlePrimaryActionForHalfScreenPromoCoordinator:
+    (HalfScreenPromoCoordinator*)coordinator;
+
+// Invoked when the user clicks on the secondary button.
+- (void)handleSecondaryActionForHalfScreenPromoCoordinator:
+    (HalfScreenPromoCoordinator*)coordinator;
+
+// Invoked when the user dismiss the promo.
+- (void)handleDismissActionForHalfScreenPromoCoordinator:
+    (HalfScreenPromoCoordinator*)coordinator;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_COORDINATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.h b/ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.h
new file mode 100644
index 0000000..6e69589
--- /dev/null
+++ b/ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.h
@@ -0,0 +1,20 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"
+
+// View controller for the Half Screen Default Browser Video promo.
+@interface HalfScreenPromoViewController : UIViewController
+
+// The action handler for interactions in this view controller.
+@property(nonatomic, weak) id<ConfirmationAlertActionHandler> actionHandler;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_DEFAULT_PROMO_HALF_SCREEN_PROMO_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.mm b/ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.mm
new file mode 100644
index 0000000..040d06c
--- /dev/null
+++ b/ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.mm
@@ -0,0 +1,91 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/default_promo/half_screen_promo_view_controller.h"
+
+#import "base/values.h"
+#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h"
+#import "ios/chrome/grit/ios_google_chrome_strings.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ios/public/provider/chrome/browser/branded_images/branded_images_api.h"
+#import "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+constexpr CGFloat kCustomSpacingBeforeImageIfNoNavigationBar = 32;
+constexpr CGFloat kCustomSpacingAfterImageWithoutAnimation = 24;
+constexpr CGFloat kCustomFaviconSideLength = 56;
+constexpr CGFloat kPreferredCornerRadius = 20;
+}  // namespace
+
+@interface HalfScreenPromoViewController ()
+
+// Child view controller used to display the alert screen for the half-screen
+// and full-screen promos.
+@property(nonatomic, strong) ConfirmationAlertViewController* alertScreen;
+
+@end
+
+@implementation HalfScreenPromoViewController
+
+#pragma mark - UIViewController
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  [self addChildViewController:self.alertScreen];
+  [self.view addSubview:self.alertScreen.view];
+  [self.alertScreen didMoveToParentViewController:self];
+  [self layoutAlertScreen];
+}
+
+#pragma mark - Private
+
+// Configures the alertScreen view.
+- (ConfirmationAlertViewController*)alertScreen {
+  if (!_alertScreen) {
+    _alertScreen = [[ConfirmationAlertViewController alloc] init];
+    _alertScreen.titleString = l10n_util::GetNSString(
+        IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_TITLE_TEXT);
+    _alertScreen.subtitleString = l10n_util::GetNSString(
+        IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SUBTITLE_TEXT);
+    _alertScreen.primaryActionString = l10n_util::GetNSString(
+        IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_PRIMARY_BUTTON_TEXT);
+    _alertScreen.secondaryActionString = l10n_util::GetNSString(
+        IDS_IOS_DEFAULT_BROWSER_HALF_SCREEN_PROMO_SECONDARY_BUTTON_TEXT);
+    _alertScreen.image = ios::provider::GetBrandedImage(
+        ios::provider::BrandedImage::kNonModalDefaultBrowserPromo);
+    _alertScreen.actionHandler = self.actionHandler;
+    _alertScreen.imageHasFixedSize = YES;
+    _alertScreen.showDismissBarButton = NO;
+    _alertScreen.titleTextStyle = UIFontTextStyleTitle2;
+    _alertScreen.customSpacingBeforeImageIfNoNavigationBar =
+        kCustomSpacingBeforeImageIfNoNavigationBar;
+    _alertScreen.topAlignedLayout = YES;
+    _alertScreen.imageEnclosedWithShadowWithoutBadge = YES;
+    _alertScreen.customFaviconSideLength = kCustomFaviconSideLength;
+    _alertScreen.customSpacingAfterImage =
+        kCustomSpacingAfterImageWithoutAnimation;
+  }
+
+  return _alertScreen;
+}
+
+// Sets the layout of the alertScreen view when the promo will be
+// shown without the animation view (half-screen promo).
+- (void)layoutAlertScreen {
+  self.alertScreen.modalPresentationStyle = UIModalPresentationPageSheet;
+  UISheetPresentationController* presentationController =
+      self.alertScreen.sheetPresentationController;
+  presentationController.prefersEdgeAttachedInCompactHeight = YES;
+  presentationController.detents = @[
+    UISheetPresentationControllerDetent.mediumDetent,
+    UISheetPresentationControllerDetent.largeDetent
+  ];
+  presentationController.preferredCornerRadius = kPreferredCornerRadius;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/first_run/BUILD.gn b/ios/chrome/browser/ui/first_run/BUILD.gn
index 65d17d0..4e29ed97 100644
--- a/ios/chrome/browser/ui/first_run/BUILD.gn
+++ b/ios/chrome/browser/ui/first_run/BUILD.gn
@@ -82,6 +82,7 @@
     "//ios/chrome/browser/ui/authentication/views",
     "//ios/chrome/browser/ui/fancy_ui",
     "//ios/chrome/browser/ui/first_run/default_browser",
+    "//ios/chrome/browser/ui/first_run/history_sync",
     "//ios/chrome/browser/ui/first_run/signin",
     "//ios/chrome/browser/ui/first_run/tangible_sync",
     "//ios/chrome/browser/ui/first_run/tos",
diff --git a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
index cf19ecc..6c95fb0 100644
--- a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
@@ -16,6 +16,7 @@
 #import "ios/chrome/browser/ui/first_run/default_browser/default_browser_screen_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/first_run_screen_delegate.h"
 #import "ios/chrome/browser/ui/first_run/first_run_util.h"
+#import "ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/tangible_sync/tangible_sync_screen_coordinator.h"
 #import "ios/chrome/browser/ui/screen/screen_provider.h"
@@ -136,6 +137,12 @@
                                                ACCESS_POINT_START_PAGE
                                promoAction:signin_metrics::PromoAction::
                                                PROMO_ACTION_NO_SIGNIN_PROMO];
+    case kHistorySync:
+      return [[HistorySyncScreenCoordinator alloc]
+          initWithBaseNavigationController:self.navigationController
+                                   browser:self.browser
+                                  firstRun:YES
+                                  delegate:self];
     case kTangibleSync:
       return [[TangibleSyncScreenCoordinator alloc]
           initWithBaseNavigationController:self.navigationController
diff --git a/ios/chrome/browser/ui/first_run/history_sync/BUILD.gn b/ios/chrome/browser/ui/first_run/history_sync/BUILD.gn
new file mode 100644
index 0000000..74c3b7d
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/history_sync/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("history_sync") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "history_sync_screen_coordinator.h",
+    "history_sync_screen_coordinator.mm",
+  ]
+  deps = [
+    "//base:base",
+    "//ios/chrome/browser/shared/model/browser",
+    "//ios/chrome/browser/shared/model/browser_state",
+    "//ios/chrome/browser/ui/first_run:interruptible_chrome_coordinator",
+    "//ios/chrome/browser/ui/first_run:screen_delegate",
+  ]
+  frameworks = [ "UIKit.framework" ]
+}
diff --git a/ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.h b/ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.h
new file mode 100644
index 0000000..4bd4b6a
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.h
@@ -0,0 +1,32 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_FIRST_RUN_HISTORY_SYNC_HISTORY_SYNC_SCREEN_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_FIRST_RUN_HISTORY_SYNC_HISTORY_SYNC_SCREEN_COORDINATOR_H_
+
+#import "ios/chrome/browser/ui/first_run/first_run_screen_delegate.h"
+#import "ios/chrome/browser/ui/first_run/interruptible_chrome_coordinator.h"
+
+// Coordinator to present the history sync screen.
+@interface HistorySyncScreenCoordinator : InterruptibleChromeCoordinator
+
+// Initiates a SyncScreenCoordinator with
+// `navigationController` to present the view;
+// `browser` to provide the browser;
+// `firstRun` to determine whether this is used in the FRE;
+// `delegate` to handle user action.
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+                                        firstRun:(BOOL)firstRun
+                                        delegate:
+                                            (id<FirstRunScreenDelegate>)delegate
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser NS_UNAVAILABLE;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_FIRST_RUN_HISTORY_SYNC_HISTORY_SYNC_SCREEN_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.mm
new file mode 100644
index 0000000..cfacb84
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.mm
@@ -0,0 +1,37 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/first_run/history_sync/history_sync_screen_coordinator.h"
+
+#import "ios/chrome/browser/shared/model/browser/browser.h"
+#import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation HistorySyncScreenCoordinator {
+  __weak id<FirstRunScreenDelegate> _delegate;
+  BOOL _firstRun;
+}
+
+@synthesize baseNavigationController = _baseNavigationController;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+                                        firstRun:(BOOL)firstRun
+                                        delegate:(id<FirstRunScreenDelegate>)
+                                                     delegate {
+  self = [super initWithBaseViewController:navigationController
+                                   browser:browser];
+  if (self) {
+    _baseNavigationController = navigationController;
+    _delegate = delegate;
+    _firstRun = firstRun;
+  }
+  return self;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/infobars/modals/autofill_address_profile/infobar_edit_address_profile_table_view_controller_unittest.mm b/ios/chrome/browser/ui/infobars/modals/autofill_address_profile/infobar_edit_address_profile_table_view_controller_unittest.mm
index 5a555c9..75f0ad9 100644
--- a/ios/chrome/browser/ui/infobars/modals/autofill_address_profile/infobar_edit_address_profile_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/infobars/modals/autofill_address_profile/infobar_edit_address_profile_table_view_controller_unittest.mm
@@ -153,6 +153,14 @@
 // Tests the edit view initialisation for a profile in the save prompt.
 TEST_F(InfobarEditAddressProfileTableViewControllerTest,
        TestEditViewForProfile) {
+  if (base::FeatureList::IsEnabled(
+          autofill::features::kAutofillAccountProfilesUnionView)) {
+    // The test is incompatible with the feature as the country is a selection
+    // field.
+    // TODO(crbug.com/1423319): Cleanup
+    return;
+  }
+
   TableViewModel* model = [controller() tableViewModel];
 
   autofill::AutofillProfile profile = autofill::test::GetFullProfile2();
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_header_view_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_header_view_controller.mm
index bdf3722..bbd913ac 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_header_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_header_view_controller.mm
@@ -643,6 +643,10 @@
       content_suggestions::HeightForLogoHeader(
           self.logoIsShowing, self.logoVendor.isShowingDoodle, [self topInset],
           self.traitCollection);
+  // Trigger relayout so that it immediately returns the updated content height
+  // for the NTP to update content inset.
+  [self.view setNeedsLayout];
+  [self.view layoutIfNeeded];
   [self.commandHandler updateForHeaderSizeChange];
 }
 
diff --git a/ios/chrome/browser/ui/screen/screen_type.h b/ios/chrome/browser/ui/screen/screen_type.h
index e9e26f4..4c7fa894 100644
--- a/ios/chrome/browser/ui/screen/screen_type.h
+++ b/ios/chrome/browser/ui/screen/screen_type.h
@@ -8,6 +8,7 @@
 // The types of the start up screens.
 typedef NS_ENUM(NSInteger, ScreenType) {
   kSignIn,
+  kHistorySync,
   kTangibleSync,
   kDefaultBrowserPromo,
 
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm
index 6fff722..2c5a1b9 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller_unittest.mm
@@ -131,6 +131,14 @@
 
 // Deleting the only profile results in item deletion and section deletion.
 TEST_F(AutofillProfileTableViewControllerTest, TestOneProfileItemDeleted) {
+  if (base::FeatureList::IsEnabled(
+          autofill::features::kAutofillAccountProfilesUnionView)) {
+    // The test is incompatible with the feature as now the user is asked to
+    // confirm the deletion.
+    // TODO(crbug.com/1423319): Cleanup
+    return;
+  }
+
   AddProfile("John Doe", "1 Main Street");
   CreateController();
   CheckController();
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_settings_profile_edit_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/autofill/autofill_settings_profile_edit_table_view_controller_unittest.mm
index 2aae3c0b..c20829d8 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_settings_profile_edit_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_settings_profile_edit_table_view_controller_unittest.mm
@@ -123,6 +123,14 @@
 
 // Adding a single address results in an address section.
 TEST_F(AutofillSettingsProfileEditTableViewControllerTest, TestOneProfile) {
+  if (base::FeatureList::IsEnabled(
+          autofill::features::kAutofillAccountProfilesUnionView)) {
+    // The test is incompatible with the feature as the country is a selection
+    // field.
+    // TODO(crbug.com/1423319): Cleanup
+    return;
+  }
+
   TableViewModel* model = [controller() tableViewModel];
 
   autofill::AutofillProfile profile = autofill::test::GetFullProfile2();
diff --git a/ios/chrome/browser/ui/settings/password/account_storage_utils.cc b/ios/chrome/browser/ui/settings/password/account_storage_utils.cc
index 1f42f21..13607e6 100644
--- a/ios/chrome/browser/ui/settings/password/account_storage_utils.cc
+++ b/ios/chrome/browser/ui/settings/password/account_storage_utils.cc
@@ -31,9 +31,10 @@
     return false;
   }
 
-  // `credential` is only stored in the profile store. Unfortunately this has
-  // different meanings depending whether sync-the-feature is enabled or not.
-  if (sync_service->IsSyncFeatureEnabled()) {
+  // Syncing and signed-out users shouldn't see the icon.
+  if (sync_service->IsSyncFeatureEnabled() ||
+      sync_service->HasDisableReason(
+          syncer::SyncService::DisableReason::DISABLE_REASON_NOT_SIGNED_IN)) {
     return false;
   }
   return base::FeatureList::IsEnabled(
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
index d832378f..ad8ad3d 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
@@ -93,7 +93,7 @@
             static_cast<int>(GetParam().incognitoModeAvailability)));
 
     // TODO(crbug.com/1443624): Remove when feature is enabled by default.
-    feature_list_.InitAndEnableFeature(web::kEnableBrowserLockdownMode);
+    feature_list_.InitAndEnableFeature(web::kBrowserLockdownModeAvailable);
   }
 
   void TearDown() override {
@@ -156,7 +156,7 @@
     expectedNumberOfSections++;
   }
 
-  if (base::FeatureList::IsEnabled(web::kEnableBrowserLockdownMode)) {
+  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
     expectedNumberOfSections++;
   }
 
@@ -179,7 +179,7 @@
       SafeBrowsingDetailText(), currentSection, 0);
 
   // Lockdown Mode section.
-  if (base::FeatureList::IsEnabled(web::kEnableBrowserLockdownMode)) {
+  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
     currentSection++;
     EXPECT_EQ(1, NumberOfItemsInSection(currentSection));
     CheckTextCellTextAndDetailText(
@@ -257,7 +257,7 @@
     expectedNumberOfSections++;
   }
 
-  if (base::FeatureList::IsEnabled(web::kEnableBrowserLockdownMode)) {
+  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
     expectedNumberOfSections++;
   }
 
@@ -287,7 +287,7 @@
     expectedNumberOfSections++;
   }
 
-  if (base::FeatureList::IsEnabled(web::kEnableBrowserLockdownMode)) {
+  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
     expectedNumberOfSections++;
   }
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
index 513d061..ef57fea 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
@@ -563,7 +563,8 @@
       // complete, reset the tab grid mode.
       self.baseViewController.tabGridMode = TabGridModeNormal;
     }
-    if (!GetFirstResponder()) {
+    if (!GetFirstResponderInWindowScene(
+            self.baseViewController.view.window.windowScene)) {
       // It is possible to already have a first responder (for example the
       // omnibox). In that case, we don't want to mark BVC as first responder.
       [self.bvcContainer.currentBVC becomeFirstResponder];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
index 6c796e0..022e3524 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -314,12 +314,6 @@
   [super viewDidLayoutSubviews];
   // Modify Incognito and Regular Tabs Insets.
   [self setInsetForGridViews];
-  // Reset bottom message width after bottom toolbar is updated after an
-  // orientation change. As this depends on
-  // `regularTabsViewController.gridView.contentOffset.x`, this should not be
-  // done in `-traitCollectionDidChange` when the updated layout has not been
-  // finalized.
-  [self updateRegularTabsBottomMessageConstraintsIfExists];
 }
 
 - (void)viewWillTransitionToSize:(CGSize)size
@@ -355,6 +349,7 @@
   if (IsPinnedTabsEnabled()) {
     [self updatePinnedTabsViewControllerConstraints];
   }
+  [self updateRegularTabsBottomMessageConstraintsIfExists];
 }
 
 #pragma mark - UIScrollViewDelegate
@@ -3179,6 +3174,7 @@
   self.regularTabsBottomMessageConstraints = nil;
 
   UIView* bottomMessageView = self.regularTabsBottomMessage.view;
+  [bottomMessageView invalidateIntrinsicContentSize];
   NSMutableArray<NSLayoutConstraint*>* constraints =
       [[NSMutableArray alloc] init];
   // left and right anchors.
@@ -3223,7 +3219,10 @@
     [bottomMessageView.bottomAnchor constraintEqualToAnchor:bottomAnchor],
     [bottomMessageView.topAnchor
         constraintGreaterThanOrEqualToAnchor:self.view.topAnchor
-                                    constant:topLayoutAnchorConstant]
+                                    constant:topLayoutAnchorConstant],
+    [bottomMessageView.heightAnchor
+        constraintLessThanOrEqualToConstant:bottomMessageView
+                                                .intrinsicContentSize.height],
   ]];
   self.regularTabsBottomMessageConstraints = constraints;
   [NSLayoutConstraint
diff --git a/ios/chrome/browser/url/BUILD.gn b/ios/chrome/browser/url/BUILD.gn
index 84bf1706..d7c9e35 100644
--- a/ios/chrome/browser/url/BUILD.gn
+++ b/ios/chrome/browser/url/BUILD.gn
@@ -29,6 +29,7 @@
 
   deps = [
     "//base",
+    "//components/commerce/core:commerce_constants",
     "//components/optimization_guide/optimization_guide_internals/webui:url_constants",
     "//ios/components/webui:url_constants",
   ]
diff --git a/ios/chrome/browser/url/chrome_url_constants.cc b/ios/chrome/browser/url/chrome_url_constants.cc
index 871ab77..e9f44b7 100644
--- a/ios/chrome/browser/url/chrome_url_constants.cc
+++ b/ios/chrome/browser/url/chrome_url_constants.cc
@@ -8,6 +8,7 @@
 
 #include <iterator>
 
+#include "components/commerce/core/commerce_constants.h"
 #include "components/optimization_guide/optimization_guide_internals/webui/url_constants.h"
 #include "ios/components/webui/web_ui_url_constants.h"
 
@@ -64,6 +65,7 @@
 // These hosts will also be suggested by BuiltinProvider.
 // 'histograms' is chrome WebUI on iOS, content WebUI on other platforms.
 const char* const kChromeHostURLs[] = {
+    commerce::kChromeUICommerceInternalsHost,
     kChromeUIChromeURLsHost,
     kChromeUICreditsHost,
     kChromeUIFlagsHost,
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index e67d720..27fdfc83 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -512,7 +512,7 @@
 
 bool ChromeWebClient::IsBrowserLockdownModeEnabled(
     web::BrowserState* browser_state) {
-  if (base::FeatureList::IsEnabled(web::kEnableBrowserLockdownMode)) {
+  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
     ChromeBrowserState* chrome_browser_state =
         ChromeBrowserState::FromBrowserState(browser_state);
     PrefService* prefs = chrome_browser_state->GetPrefs();
diff --git a/ios/chrome/browser/web/features.cc b/ios/chrome/browser/web/features.cc
index 29171b8..3540cda 100644
--- a/ios/chrome/browser/web/features.cc
+++ b/ios/chrome/browser/web/features.cc
@@ -8,12 +8,12 @@
 
 namespace web {
 
-BASE_FEATURE(kEnableBrowserLockdownMode,
-             "EnableBrowserLockdownMode",
+BASE_FEATURE(kBrowserLockdownModeAvailable,
+             "BrowserLockdownModeAvailable",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 bool IsBrowserLockdownModeEnabled() {
-  return base::FeatureList::IsEnabled(kEnableBrowserLockdownMode);
+  return base::FeatureList::IsEnabled(kBrowserLockdownModeAvailable);
 }
 
 BASE_FEATURE(kWebPageDefaultZoomFromDynamicType,
diff --git a/ios/chrome/browser/web/features.h b/ios/chrome/browser/web/features.h
index 8b3575c..2140e9d3 100644
--- a/ios/chrome/browser/web/features.h
+++ b/ios/chrome/browser/web/features.h
@@ -10,7 +10,7 @@
 namespace web {
 
 // Feature flag to enable lockdown mode within browser.
-BASE_DECLARE_FEATURE(kEnableBrowserLockdownMode);
+BASE_DECLARE_FEATURE(kBrowserLockdownModeAvailable);
 
 // Returns true if the use of Browser Lockdown Mode is enabled.
 bool IsBrowserLockdownModeEnabled();
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
index 8a29543..54061dac 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
@@ -65,6 +65,9 @@
 // set before the view is loaded.
 @property(nonatomic, assign) CGFloat customSpacingAfterImage;
 
+// Sets the custom size for the favicon.
+@property(nonatomic, assign) CGFloat customFaviconSideLength;
+
 // Sets the custom spacing of the stackview. Values for
 // `customSpacingBeforeImageIfNoNavigationBar` and `customSpacingAfterImage` are
 // honored around the image, so this applies to all the other items of the
@@ -86,6 +89,10 @@
 // with a green checkmark. Must be set before the view is loaded. Default is NO.
 @property(nonatomic) BOOL imageEnclosedWithShadowAndBadge;
 
+// Set to YES to enclose the image in a frame with a shadow without a corner
+// green checkmark badge. Must be set before the view is loaded. Default is NO.
+@property(nonatomic, assign) BOOL imageEnclosedWithShadowWithoutBadge;
+
 // Set to NO to prevent the scroll view from showing a vertical scrollbar
 // indicator. Must be set before the view is loaded. Default is YES.
 @property(nonatomic) BOOL showsVerticalScrollIndicator;
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
index 49c3ca3..49f28b8 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
@@ -108,7 +108,8 @@
     [self.view addSubview:self.navigationBar];
   }
 
-  if (self.imageEnclosedWithShadowAndBadge) {
+  if (self.imageEnclosedWithShadowAndBadge ||
+      self.imageEnclosedWithShadowWithoutBadge) {
     // The image view is set within the helper method.
     self.imageContainerView = [self createImageContainerViewWithShadowAndBadge];
   } else {
@@ -524,12 +525,20 @@
   [containerView addSubview:frameView];
   [containerView addSubview:faviconBadgeView];
 
+  if (self.imageEnclosedWithShadowWithoutBadge) {
+    [faviconBadgeView setHidden:YES];
+  }
+
+  CGFloat faviconSideLength = self.customFaviconSideLength > 0
+                                  ? self.customFaviconSideLength
+                                  : kFaviconSideLength;
+
   [NSLayoutConstraint activateConstraints:@[
     // Size constraints.
     [frameView.widthAnchor constraintEqualToConstant:kFaviconFrameSideLength],
     [frameView.heightAnchor constraintEqualToConstant:kFaviconFrameSideLength],
-    [faviconView.widthAnchor constraintEqualToConstant:kFaviconSideLength],
-    [faviconView.heightAnchor constraintEqualToConstant:kFaviconSideLength],
+    [faviconView.widthAnchor constraintEqualToConstant:faviconSideLength],
+    [faviconView.heightAnchor constraintEqualToConstant:faviconSideLength],
     [faviconBadgeView.widthAnchor
         constraintEqualToConstant:kFaviconBadgeSideLength],
     [faviconBadgeView.heightAnchor
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index 07167861..70497e3 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-c738e10a1efbb14ce4e7c4139449e1bd60c1faa0
\ No newline at end of file
+40df72f86c025c2be70f70562eb7e1c333009314
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index 7cd0f5f..3b646ce 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-82e48010a694ee5a710e4106418bdf365b470e80
\ No newline at end of file
+01c05ebb6d71b8b35f958a3f8c44385b33fcb9c0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index ce5528f4..0beb8b34 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-37cf59fd9cdcda099d44a272566e7d34bc6c5086
\ No newline at end of file
+27a581a4e45d4c73ced2e1192ffcef3d74d95a0b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 03af3ac9..0bb7ef32 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-04b45eb5a2a527d56ca3a04d3f87d213eb40c224
\ No newline at end of file
+4c0cebf0a5fd93c53cda9b72fc3f3f8cb90235c9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 607ef6b..c774bdf 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-905ce366f59ddf275ef22816b103949c9c10ca5f
\ No newline at end of file
+f75595eb9dc31941389d8b919440f6908a5f6990
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 2b92eb15..f1f3c91 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-d075c57e137170b01d5b499084ce4e320002f4ba
\ No newline at end of file
+fde1f5a1f5caa9277af2bb1402719aabd8e1000b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index ca955e7..f36f16a 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-a779b4b86af8c5d202466081402e186ae73cb701
\ No newline at end of file
+95354d89f0015a78d2a1c49d254a39ac100e1770
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index ce42a8af..dc2ae8fe 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-90eb011843aa40ebe006da02fe95564d163776fc
\ No newline at end of file
+c8815c32becdfb5d33416ba2498229803920571f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index f9bf902..1fcb648 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-44c2af1b77348010226698c6f283a65fa3711de7
\ No newline at end of file
+6b0e09c0ac118c7f1cd6e2b861e9537c60e0b23d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index caadca53..fe2770e 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-eb8ce938cac8b308b7dad42c0e9cc0f5c4c39390
\ No newline at end of file
+671b881473a992b2eb3c9e14042441a7d79f9d15
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 940b773..d374e01 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-8e6fea08f36333adc37c6e45ec85878dc1b5f958
\ No newline at end of file
+54ed55c461a952f0915c12baa2bbd42627681a17
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 59f078f..d3188775 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-14e12fd4317f3c446cba341057910859de552da9
\ No newline at end of file
+84bd1fe304ad507810a08b8cbab8fe02710125a8
\ No newline at end of file
diff --git a/ios/web/content/js_messaging/content_java_script_feature_manager.h b/ios/web/content/js_messaging/content_java_script_feature_manager.h
index 2210075..72a9ed6 100644
--- a/ios/web/content/js_messaging/content_java_script_feature_manager.h
+++ b/ios/web/content/js_messaging/content_java_script_feature_manager.h
@@ -5,10 +5,13 @@
 #ifndef IOS_WEB_CONTENT_JS_MESSAGING_CONTENT_JAVA_SCRIPT_FEATURE_MANAGER_H_
 #define IOS_WEB_CONTENT_JS_MESSAGING_CONTENT_JAVA_SCRIPT_FEATURE_MANAGER_H_
 
+#import <map>
 #import <set>
 #import <string>
 #import <vector>
 
+#import "ios/web/public/js_messaging/java_script_feature.h"
+
 namespace content {
 class RenderFrameHost;
 }
@@ -19,7 +22,7 @@
 
 namespace web {
 
-class JavaScriptFeature;
+class ScriptMessage;
 
 // Configures JavaScriptFeatures, by injecting document start and end scripts,
 // and owning a mapping for routing script message callbacks.
@@ -46,6 +49,12 @@
   // Returns true if this feature manager already has the given `feature`.
   bool HasFeature(const JavaScriptFeature* feature) const;
 
+  // Handles a `script_message` from JavaScript in `web_state`, directed to the
+  // given `handler_name`
+  void ScriptMessageReceived(const ScriptMessage& script_message,
+                             std::string handler_name,
+                             WebState* web_state);
+
  private:
   // Adds the given `feature` to the set of features managed by this feature
   // manager, unless the given `feature` has already been added.
@@ -54,6 +63,10 @@
   // The features which are managed by this feature manager.
   std::set<const JavaScriptFeature*> features_;
 
+  // Maps handler names to message handlers.
+  std::map<std::string, JavaScriptFeature::ScriptMessageHandler>
+      script_message_handlers_;
+
   // Scripts that are injected when the document element is created.
   std::vector<std::u16string> document_start_scripts_;
 
diff --git a/ios/web/content/js_messaging/content_java_script_feature_manager.mm b/ios/web/content/js_messaging/content_java_script_feature_manager.mm
index 13ac49c..ba13d92 100644
--- a/ios/web/content/js_messaging/content_java_script_feature_manager.mm
+++ b/ios/web/content/js_messaging/content_java_script_feature_manager.mm
@@ -11,6 +11,7 @@
 #import "content/public/browser/render_frame_host.h"
 #import "ios/web/public/js_messaging/java_script_feature.h"
 #import "ios/web/public/js_messaging/java_script_feature_util.h"
+#import "ios/web/public/js_messaging/script_message.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -91,7 +92,28 @@
     }
   }
 
-  // TODO(crbug.com/1423527): Add mapping for script message handlers.
+  absl::optional<std::string> handler_name =
+      feature->GetScriptMessageHandlerName();
+  if (handler_name) {
+    absl::optional<JavaScriptFeature::ScriptMessageHandler> handler =
+        feature->GetScriptMessageHandler();
+    CHECK(handler);
+    CHECK(!script_message_handlers_.count(*handler_name));
+    script_message_handlers_[*handler_name] = *handler;
+  }
+}
+
+void ContentJavaScriptFeatureManager::ScriptMessageReceived(
+    const ScriptMessage& script_message,
+    std::string handler_name,
+    WebState* web_state) {
+  auto it = script_message_handlers_.find(handler_name);
+  if (it == script_message_handlers_.end()) {
+    LOG(ERROR) << "No message handler for " << handler_name;
+    return;
+  }
+
+  it->second.Run(web_state, script_message);
 }
 
 }  // namespace web
diff --git a/ios/web/content/js_messaging/content_web_frames_manager.h b/ios/web/content/js_messaging/content_web_frames_manager.h
index 920dea731..b8ebe31 100644
--- a/ios/web/content/js_messaging/content_web_frames_manager.h
+++ b/ios/web/content/js_messaging/content_web_frames_manager.h
@@ -7,6 +7,7 @@
 
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 
+#import "base/memory/weak_ptr.h"
 #import "base/observer_list.h"
 #import "build/blink_buildflags.h"
 #import "content/public/browser/global_routing_id.h"
@@ -24,6 +25,7 @@
 
 class ContentJavaScriptFeatureManager;
 class ContentWebState;
+class ScriptMessage;
 
 // ContentWebFramesManager is a WebFramesManager that is built on top
 // of //content. As a WebContentsObserver, it finds out about all frame creation
@@ -55,6 +57,12 @@
   // Return the WebFrame* corresponding to the given content id.
   WebFrame* WebFrameForContentId(content::GlobalRenderFrameHostId content_id);
 
+  // Handles messages received from JavaScript. These messages are expected to
+  // contain a "handler_name" field and a "message" field, where the value of
+  // the latter field is the actual message that is passed on to the named
+  // handler.
+  void ScriptMessageReceived(const ScriptMessage& script_message);
+
   // Map of ids to owning pointers for all WebFrames.
   std::map<std::string, std::unique_ptr<WebFrame>> web_frames_;
 
@@ -77,6 +85,8 @@
 
   // Manages JavaScriptFeatures.
   std::unique_ptr<ContentJavaScriptFeatureManager> js_feature_manager_;
+
+  base::WeakPtrFactory<ContentWebFramesManager> weak_factory_{this};
 };
 
 }  // namespace web
diff --git a/ios/web/content/js_messaging/content_web_frames_manager.mm b/ios/web/content/js_messaging/content_web_frames_manager.mm
index 192a2b4..e6cd1b2 100644
--- a/ios/web/content/js_messaging/content_web_frames_manager.mm
+++ b/ios/web/content/js_messaging/content_web_frames_manager.mm
@@ -16,6 +16,7 @@
 #import "ios/web/content/js_messaging/ios_web_message_host_factory.h"
 #import "ios/web/content/web_state/content_web_state.h"
 #import "ios/web/public/js_messaging/java_script_feature_util.h"
+#import "ios/web/public/js_messaging/script_message.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -23,6 +24,15 @@
 
 namespace web {
 
+namespace {
+
+// Names of JSON dictionary properties that JavaScript populates when sending
+// messages to the browser.
+const char kHandlerNamePropertyName[] = "handler_name";
+const char kMessagePropertyName[] = "message";
+
+}  // namespace
+
 ContentWebFramesManager::ContentWebFramesManager(
     ContentWebState* content_web_state)
     : content::WebContentsObserver(content_web_state->GetWebContents()),
@@ -30,7 +40,11 @@
       js_communication_host_(
           std::make_unique<js_injection::JsCommunicationHost>(
               content_web_state->GetWebContents())) {
-  auto message_host_factory = std::make_unique<IOSWebMessageHostFactory>();
+  auto web_message_callback =
+      base::BindRepeating(&ContentWebFramesManager::ScriptMessageReceived,
+                          weak_factory_.GetWeakPtr());
+  auto message_host_factory =
+      std::make_unique<IOSWebMessageHostFactory>(web_message_callback);
   js_communication_host_->AddWebMessageHostFactory(
       std::move(message_host_factory), u"webkitMessageHandler", {"*"});
 
@@ -174,4 +188,34 @@
   return web_frame_it->second.get();
 }
 
+void ContentWebFramesManager::ScriptMessageReceived(
+    const ScriptMessage& script_message) {
+  // In ios/web/content, only a single script message handler is exposed to
+  // JavaScript. To simulate having multiple handlers (as in WKWebView-based
+  // ios/web), messages sent to this handler have the form of a dictionary with
+  // two fields, specifying the destination handler name along with the actual
+  // message for that handler. Once these two parts are extracted from
+  // `script_message`, a new ScriptMessage is constructed with only the actual
+  // message intended for the handler.
+
+  base::Value::Dict* dict = script_message.body()->GetIfDict();
+  if (!dict) {
+    return;
+  }
+
+  const std::string* handler_name = dict->FindString(kHandlerNamePropertyName);
+  base::Value* message_content = dict->Find(kMessagePropertyName);
+  if (!handler_name || !message_content) {
+    return;
+  }
+
+  ScriptMessage message_for_handler(
+      std::make_unique<base::Value>(std::move(*message_content)),
+      script_message.is_user_interacting(), script_message.is_main_frame(),
+      script_message.request_url());
+
+  js_feature_manager_->ScriptMessageReceived(message_for_handler, *handler_name,
+                                             content_web_state_);
+}
+
 }  // namespace web
diff --git a/ios/web/content/js_messaging/ios_web_message_host_factory.h b/ios/web/content/js_messaging/ios_web_message_host_factory.h
index ce408a6e..d3629f2 100644
--- a/ios/web/content/js_messaging/ios_web_message_host_factory.h
+++ b/ios/web/content/js_messaging/ios_web_message_host_factory.h
@@ -5,7 +5,10 @@
 #ifndef IOS_WEB_CONTENT_JS_MESSAGING_IOS_WEB_MESSAGE_HOST_FACTORY_H_
 #define IOS_WEB_CONTENT_JS_MESSAGING_IOS_WEB_MESSAGE_HOST_FACTORY_H_
 
+#import "base/functional/callback.h"
+#import "base/values.h"
 #import "components/js_injection/browser/web_message_host_factory.h"
+#import "ios/web/public/js_messaging/script_message.h"
 
 namespace web {
 
@@ -13,9 +16,11 @@
 // creating a WebMessageHost when JavaScript sends a message to the browser.
 class IOSWebMessageHostFactory : public js_injection::WebMessageHostFactory {
  public:
-  // TODO(crbug.com/1423527): Add a listener argument to this constructor. The
-  // listener will be called when messages are received from JavaScript.
-  IOSWebMessageHostFactory();
+  using WebMessageCallback =
+      base::RepeatingCallback<void(const ScriptMessage&)>;
+
+  // `message_callback` is called with each message received from JavaScript.
+  IOSWebMessageHostFactory(WebMessageCallback message_callback);
   IOSWebMessageHostFactory(const IOSWebMessageHostFactory&) = delete;
   IOSWebMessageHostFactory& operator=(const IOSWebMessageHostFactory&) = delete;
   ~IOSWebMessageHostFactory() override;
@@ -25,6 +30,10 @@
       const std::string& origin_string,
       bool is_main_frame,
       js_injection::WebMessageReplyProxy* proxy) override;
+
+ private:
+  // Called with each message received from JavaScript.
+  WebMessageCallback message_callback_;
 };
 
 }  // namespace web
diff --git a/ios/web/content/js_messaging/ios_web_message_host_factory.mm b/ios/web/content/js_messaging/ios_web_message_host_factory.mm
index 592af3b..038a0f61 100644
--- a/ios/web/content/js_messaging/ios_web_message_host_factory.mm
+++ b/ios/web/content/js_messaging/ios_web_message_host_factory.mm
@@ -7,6 +7,8 @@
 #import <string>
 
 #import "base/functional/overloaded.h"
+#import "base/json/json_reader.h"
+#import "base/strings/utf_string_conversions.h"
 #import "components/js_injection/browser/js_communication_host.h"
 #import "components/js_injection/browser/web_message.h"
 #import "components/js_injection/browser/web_message_host.h"
@@ -22,31 +24,65 @@
 // Created when a message is received from JavaScript.
 class IOSWebMessageHost : public js_injection::WebMessageHost {
  public:
-  IOSWebMessageHost(const std::string& origin_string, bool is_main_frame) {}
+  IOSWebMessageHost(
+      const std::string& origin_string,
+      bool is_main_frame,
+      IOSWebMessageHostFactory::WebMessageCallback message_callback)
+      : origin_string_(origin_string),
+        is_main_frame_(is_main_frame),
+        message_callback_(message_callback) {}
 
   ~IOSWebMessageHost() override = default;
 
   // js_injection::WebMessageHost:
   void OnPostMessage(
       std::unique_ptr<js_injection::WebMessage> web_message) override {
+    absl::optional<std::u16string> received_message;
     absl::visit(
         base::Overloaded{
-            [](const std::u16string& str) {
-              // TODO(crbug.com/1423527): Pass on the extracted message to
-              // feature code.
-              LOG(ERROR) << "webkitMessageHandler received: " << str;
+            [&received_message](const std::u16string& str) {
+              received_message = str;
             },
             [](const std::unique_ptr<blink::WebMessageArrayBufferPayload>&
                    array_buffer) {
               // Do nothing if the received message is not a string.
             }},
         web_message->message);
+    if (!received_message) {
+      return;
+    }
+
+    // TODO(crbug.com/1423527): Move this parsing to the renderer process.
+    absl::optional<base::Value> message_value =
+        base::JSONReader::Read(base::UTF16ToUTF8(*received_message));
+    if (!message_value) {
+      return;
+    }
+    // TODO(crbug.com/1423527): Determine whether the user has interacted with
+    // the source page.
+    bool is_user_interacting = false;
+    ScriptMessage script_message(
+        std::make_unique<base::Value>(std::move(*message_value)),
+        is_user_interacting, is_main_frame_, GURL(origin_string_));
+    message_callback_.Run(script_message);
   }
+
+ private:
+  // The origin of the page that sent the message.
+  std::string origin_string_;
+
+  // Whether the page that sent the message is a main frame.
+  bool is_main_frame_ = false;
+
+  // Called with the message received from JavaScript.
+  IOSWebMessageHostFactory::WebMessageCallback message_callback_;
 };
 
 }  // namespace
 
-IOSWebMessageHostFactory::IOSWebMessageHostFactory() = default;
+IOSWebMessageHostFactory::IOSWebMessageHostFactory(
+    WebMessageCallback message_callback)
+    : message_callback_(message_callback) {}
 
 IOSWebMessageHostFactory::~IOSWebMessageHostFactory() = default;
 
@@ -55,7 +91,8 @@
     const std::string& origin_string,
     bool is_main_frame,
     js_injection::WebMessageReplyProxy* proxy) {
-  return std::make_unique<IOSWebMessageHost>(origin_string, is_main_frame);
+  return std::make_unique<IOSWebMessageHost>(origin_string, is_main_frame,
+                                             message_callback_);
 }
 
 }  // namespace web
diff --git a/ios/web/js_messaging/web_view_js_utils.h b/ios/web/js_messaging/web_view_js_utils.h
index ffe1022..23c8218 100644
--- a/ios/web/js_messaging/web_view_js_utils.h
+++ b/ios/web/js_messaging/web_view_js_utils.h
@@ -36,7 +36,7 @@
 std::unique_ptr<base::Value> ValueResultFromWKResult(id result);
 
 // Converts base::Value to an equivalent Foundation object.
-id NSObjectFromValueResult(const base::Value& value_result);
+id NSObjectFromValueResult(const base::Value* value_result);
 
 // Executes JavaScript on WKWebView. If the web view cannot execute JS at the
 // moment, `completion_handler` is called with an NSError.
diff --git a/ios/web/js_messaging/web_view_js_utils.mm b/ios/web/js_messaging/web_view_js_utils.mm
index afb3514..26fd9234 100644
--- a/ios/web/js_messaging/web_view_js_utils.mm
+++ b/ios/web/js_messaging/web_view_js_utils.mm
@@ -77,43 +77,47 @@
 
 // Converts base::Value to an equivalent Foundation object, parsing,
 // `value_result` up to a depth of `max_depth`.
-id NSObjectFromValueResult(const base::Value& value_result, int max_depth) {
-  id result;
+id NSObjectFromValueResult(const base::Value* value_result, int max_depth) {
+  if (!value_result) {
+    return nil;
+  }
+
+  id result = nil;
 
   if (max_depth < 0) {
     DLOG(WARNING) << "JS maximum recursion depth exceeded.";
     return result;
   }
 
-  if (value_result.is_string()) {
-    result = base::SysUTF8ToNSString(value_result.GetString());
+  if (value_result->is_string()) {
+    result = base::SysUTF8ToNSString(value_result->GetString());
     DCHECK([result isKindOfClass:[NSString class]]);
-  } else if (value_result.is_int()) {
-    result = [NSNumber numberWithInt:value_result.GetInt()];
+  } else if (value_result->is_int()) {
+    result = [NSNumber numberWithInt:value_result->GetInt()];
     DCHECK([result isKindOfClass:[NSNumber class]]);
-  } else if (value_result.is_double()) {
-    result = [NSNumber numberWithDouble:value_result.GetDouble()];
+  } else if (value_result->is_double()) {
+    result = [NSNumber numberWithDouble:value_result->GetDouble()];
     DCHECK([result isKindOfClass:[NSNumber class]]);
-  } else if (value_result.is_bool()) {
-    result = [NSNumber numberWithBool:value_result.GetBool()];
+  } else if (value_result->is_bool()) {
+    result = [NSNumber numberWithBool:value_result->GetBool()];
     DCHECK([result isKindOfClass:[NSNumber class]]);
-  } else if (value_result.is_none()) {
+  } else if (value_result->is_none()) {
     result = [NSNull null];
     DCHECK([result isKindOfClass:[NSNull class]]);
-  } else if (value_result.is_dict()) {
+  } else if (value_result->is_dict()) {
     NSMutableDictionary* dictionary = [[NSMutableDictionary alloc] init];
-    for (const auto pair : value_result.GetDict()) {
+    for (const auto pair : value_result->GetDict()) {
       NSString* key = base::SysUTF8ToNSString(pair.first);
-      id wk_result = NSObjectFromValueResult(pair.second, max_depth - 1);
+      id wk_result = NSObjectFromValueResult(&pair.second, max_depth - 1);
       if (wk_result) {
         [dictionary setValue:wk_result forKey:key];
       }
     }
     result = [dictionary copy];
-  } else if (value_result.is_list()) {
+  } else if (value_result->is_list()) {
     NSMutableArray* array = [[NSMutableArray alloc] init];
-    for (const base::Value& value : value_result.GetList()) {
-      id wk_result = NSObjectFromValueResult(value, max_depth - 1);
+    for (const base::Value& value : value_result->GetList()) {
+      id wk_result = NSObjectFromValueResult(&value, max_depth - 1);
       if (wk_result) {
         [array addObject:wk_result];
       }
@@ -151,7 +155,7 @@
   return ::ValueResultFromWKResult(wk_result, kMaximumParsingRecursionDepth);
 }
 
-id NSObjectFromValueResult(const base::Value& value_result) {
+id NSObjectFromValueResult(const base::Value* value_result) {
   return ::NSObjectFromValueResult(value_result, kMaximumParsingRecursionDepth);
 }
 
diff --git a/ios/web/js_messaging/web_view_js_utils_unittest.mm b/ios/web/js_messaging/web_view_js_utils_unittest.mm
index 829e404..711cb4fc 100644
--- a/ios/web/js_messaging/web_view_js_utils_unittest.mm
+++ b/ios/web/js_messaging/web_view_js_utils_unittest.mm
@@ -261,9 +261,16 @@
   EXPECT_FALSE(current_list);
 }
 
+TEST_F(WebViewJsUtilsTest, NSObjectFromNullptr) {
+  id wk_result = web::NSObjectFromValueResult(nullptr);
+  // `wk_result` should be nil.
+  EXPECT_FALSE(wk_result);
+}
+
 // Tests that NSObjectFromValueResult converts Value::Type::STRING to NSString.
 TEST_F(WebViewJsUtilsTest, NSObjectFromStringValueResult) {
-  id wk_result = web::NSObjectFromValueResult(base::Value("test"));
+  auto value = std::make_unique<base::Value>("test");
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSString class]]);
   EXPECT_NSEQ(@"test", wk_result);
@@ -271,7 +278,8 @@
 
 // Tests that NSObjectFromValueResult converts Value::Type::INT to NSNumber.
 TEST_F(WebViewJsUtilsTest, NSObjectFromIntValueResult) {
-  id wk_result = web::NSObjectFromValueResult(base::Value(1));
+  auto value = std::make_unique<base::Value>(1);
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]);
   EXPECT_EQ(1, [wk_result intValue]);
@@ -279,7 +287,8 @@
 
 // Tests that NSObjectFromValueResult converts Value::Type::DOUBLE to NSNumber.
 TEST_F(WebViewJsUtilsTest, NSObjectFromDoubleValueResult) {
-  id wk_result = web::NSObjectFromValueResult(base::Value(3.14));
+  auto value = std::make_unique<base::Value>(3.14);
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]);
   EXPECT_EQ(3.14, [wk_result doubleValue]);
@@ -287,12 +296,14 @@
 
 // Tests that NSObjectFromValueResult converts Value::Type::BOOLEAN to NSNumber.
 TEST_F(WebViewJsUtilsTest, NSObjectFromBoolValueResult) {
-  id wk_result = web::NSObjectFromValueResult(base::Value(true));
+  auto value = std::make_unique<base::Value>(true);
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]);
   EXPECT_EQ(YES, [wk_result boolValue]);
 
-  wk_result = web::NSObjectFromValueResult(base::Value(false));
+  value.reset(new base::Value(false));
+  wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]);
   EXPECT_EQ(NO, [wk_result boolValue]);
@@ -300,7 +311,8 @@
 
 // Tests that NSObjectFromValueResult converts Value::Type::NONE to NSNull.
 TEST_F(WebViewJsUtilsTest, NSObjectFromNoneValueResult) {
-  id wk_result = web::NSObjectFromValueResult(base::Value());
+  auto value = std::make_unique<base::Value>();
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSNull class]]);
 }
@@ -315,8 +327,8 @@
   inner_test_dict.Set("Key3", 42);
   test_dict.Set("Key2", std::move(inner_test_dict));
 
-  id wk_result =
-      web::NSObjectFromValueResult(base::Value(std::move(test_dict)));
+  auto value = std::make_unique<base::Value>(std::move(test_dict));
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSDictionary class]]);
 
@@ -340,8 +352,8 @@
 
   test_list.Append(42);
 
-  id wk_result =
-      web::NSObjectFromValueResult(base::Value(std::move(test_list)));
+  auto value = std::make_unique<base::Value>(std::move(test_list));
+  id wk_result = web::NSObjectFromValueResult(value.get());
   EXPECT_TRUE(wk_result);
   EXPECT_TRUE([wk_result isKindOfClass:[NSArray class]]);
 
diff --git a/ios/web/security/crw_cert_verification_controller_unittest.mm b/ios/web/security/crw_cert_verification_controller_unittest.mm
index a69373b2..8fc5660d1 100644
--- a/ios/web/security/crw_cert_verification_controller_unittest.mm
+++ b/ios/web/security/crw_cert_verification_controller_unittest.mm
@@ -4,7 +4,7 @@
 
 #import "ios/web/security/crw_cert_verification_controller.h"
 
-#import "base/mac/bridging.h"
+#import "base/apple/bridging.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
 #import "ios/web/public/session/session_certificate_policy_cache.h"
@@ -48,10 +48,10 @@
             cert_.get()));
     ASSERT_TRUE(chain);
     valid_trust_ = web::CreateServerTrustFromChain(
-        base::mac::CFToNSPtrCast(chain.get()), kHostName);
+        base::apple::CFToNSPtrCast(chain.get()), kHostName);
     web::EnsureFutureTrustEvaluationSucceeds(valid_trust_.get());
     invalid_trust_ = web::CreateServerTrustFromChain(
-        base::mac::CFToNSPtrCast(chain.get()), kHostName);
+        base::apple::CFToNSPtrCast(chain.get()), kHostName);
   }
 
   // Synchronously returns result of
diff --git a/ios/web/security/crw_ssl_status_updater_unittest.mm b/ios/web/security/crw_ssl_status_updater_unittest.mm
index 5e70dbec..0240815 100644
--- a/ios/web/security/crw_ssl_status_updater_unittest.mm
+++ b/ios/web/security/crw_ssl_status_updater_unittest.mm
@@ -6,7 +6,7 @@
 
 #import <WebKit/WebKit.h>
 
-#import "base/mac/bridging.h"
+#import "base/apple/bridging.h"
 #import "base/strings/sys_string_conversions.h"
 #import "ios/web/navigation/navigation_manager_impl.h"
 #import "ios/web/public/navigation/navigation_item.h"
@@ -108,7 +108,7 @@
         net::x509_util::CreateSecCertificateArrayForX509Certificate(
             cert.get()));
     ASSERT_TRUE(chain);
-    trust_ = CreateServerTrustFromChain(base::mac::CFToNSPtrCast(chain.get()),
+    trust_ = CreateServerTrustFromChain(base::apple::CFToNSPtrCast(chain.get()),
                                         kHostName);
   }
 
diff --git a/ios/web/security/wk_web_view_security_util_unittest.mm b/ios/web/security/wk_web_view_security_util_unittest.mm
index 75ebc57..9e852a7 100644
--- a/ios/web/security/wk_web_view_security_util_unittest.mm
+++ b/ios/web/security/wk_web_view_security_util_unittest.mm
@@ -9,7 +9,7 @@
 
 #import <memory>
 
-#import "base/mac/bridging.h"
+#import "base/apple/bridging.h"
 #import "base/mac/foundation_util.h"
 #import "base/mac/scoped_cftyperef.h"
 #import "crypto/rsa_private_key.h"
@@ -61,7 +61,7 @@
 base::ScopedCFTypeRef<SecTrustRef> CreateTestTrust(NSArray* cert_chain) {
   base::ScopedCFTypeRef<SecPolicyRef> policy(SecPolicyCreateBasicX509());
   SecTrustRef trust = nullptr;
-  SecTrustCreateWithCertificates(base::mac::NSToCFPtrCast(cert_chain), policy,
+  SecTrustCreateWithCertificates(base::apple::NSToCFPtrCast(cert_chain), policy,
                                  &trust);
   return base::ScopedCFTypeRef<SecTrustRef>(trust);
 }
diff --git a/ios/web/web_view/error_translation_util_unittest.mm b/ios/web/web_view/error_translation_util_unittest.mm
index 5fd360e..d91c6559 100644
--- a/ios/web/web_view/error_translation_util_unittest.mm
+++ b/ios/web/web_view/error_translation_util_unittest.mm
@@ -6,7 +6,7 @@
 
 #import <Foundation/Foundation.h>
 
-#import "base/mac/bridging.h"
+#import "base/apple/bridging.h"
 #import "ios/net/protocol_handler_util.h"
 #import "ios/web/test/test_url_constants.h"
 #import "net/base/mac/url_conversions.h"
@@ -74,7 +74,7 @@
 // underlying error.
 TEST_F(ErrorTranslationUtilTest, UnknownCFNetworkError) {
   NSError* error = [[NSError alloc]
-      initWithDomain:base::mac::CFToNSPtrCast(kCFErrorDomainCFNetwork)
+      initWithDomain:base::apple::CFToNSPtrCast(kCFErrorDomainCFNetwork)
                 code:kCFURLErrorUnknown
             userInfo:nil];
   NSError* net_error = NetErrorFromError(error);
diff --git a/media/capture/video/mac/uvc_control_mac.mm b/media/capture/video/mac/uvc_control_mac.mm
index b18caff..cf1d9f24 100644
--- a/media/capture/video/mac/uvc_control_mac.mm
+++ b/media/capture/video/mac/uvc_control_mac.mm
@@ -6,7 +6,7 @@
 
 #include <IOKit/IOCFPlugIn.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_util.h"
 #include "base/mac/scoped_cftyperef.h"
@@ -107,9 +107,9 @@
   base::ScopedCFTypeRef<CFMutableDictionaryRef> query_dictionary(
       IOServiceMatching(kIOUSBDeviceClassName));
   CFDictionarySetValue(query_dictionary, CFSTR(kUSBVendorName),
-                       base::mac::NSToCFPtrCast(@(vendor_id)));
+                       base::apple::NSToCFPtrCast(@(vendor_id)));
   CFDictionarySetValue(query_dictionary, CFSTR(kUSBProductName),
-                       base::mac::NSToCFPtrCast(@(product_id)));
+                       base::apple::NSToCFPtrCast(@(product_id)));
 
   kern_return_t kr = IOServiceGetMatchingServices(
       kIOMasterPortDefault, query_dictionary.release(), usb_iterator);
diff --git a/media/cdm/win/test/media_foundation_clear_key_decryptor.cc b/media/cdm/win/test/media_foundation_clear_key_decryptor.cc
index f67376c..3974ea5 100644
--- a/media/cdm/win/test/media_foundation_clear_key_decryptor.cc
+++ b/media/cdm/win/test/media_foundation_clear_key_decryptor.cc
@@ -588,60 +588,53 @@
     DVLOG_FUNC(3) << "Clear sample detected!";
 
     output_samples[0].pSample = sample_.Detach();
-  } else {
-    // Convert the Media Foundation sample to a DecoderBuffer.
-    scoped_refptr<DecoderBuffer> encrypted_buffer;
-    RETURN_IF_FAILED(GenerateDecoderBufferFromSample(
-        sample_.Detach(), &key_id_guid, &encrypted_buffer));
-    DVLOG_FUNC(3) << "encrypted_buffer=" +
-                         encrypted_buffer->AsHumanReadableString(true);
-
-    // Decrypt the protected content.
-    Decryptor::Status decryptor_status = Decryptor::kError;
-
-    // TODO(crbug.com/1442373): We may remove the tracking code of stream type
-    // if two decryptors get created for audio and video respectively.
-    CHECK(stream_type_ != StreamType::kUnknown);
-    Decryptor::StreamType stream_type = stream_type_ == StreamType::kVideo
-                                            ? Decryptor::kVideo
-                                            : Decryptor::kAudio;
-    scoped_refptr<DecoderBuffer> decrypted_buffer;
-    bool is_decrypt_completed = false;
-    aes_decryptor_->Decrypt(
-        stream_type, encrypted_buffer,
-        base::BindOnce(
-            [](Decryptor::Status* status_copy,
-               scoped_refptr<DecoderBuffer>* buffer_copy,
-               bool* is_decrypt_completed, Decryptor::Status status,
-               scoped_refptr<DecoderBuffer> buffer) {
-              *status_copy = status;
-              *buffer_copy = std::move(buffer);
-              *is_decrypt_completed = true;
-            },
-            &decryptor_status, &decrypted_buffer, &is_decrypt_completed));
-
-    // Ensure the decryption is done synchronously.
-    CHECK(is_decrypt_completed);
-
-    // Convert the DecoderBuffer back to a Media Foundation sample.
-    ComPtr<IMFSample> sample_decrypted = nullptr;
-    GUID last_key_id = GUID_NULL;
-    RETURN_IF_FAILED(GenerateSampleFromDecoderBuffer(
-        decrypted_buffer.get(), &sample_decrypted, &last_key_id,
-        base::NullCallback()));
-    DVLOG_FUNC(3) << "decrypted_buffer=" +
-                         decrypted_buffer->AsHumanReadableString(true);
-
-    output_samples[0].pSample = sample_decrypted.Detach();
+    return S_OK;
   }
 
-  // TODO(crbug.com/1421444): Playback never ends with a sync Media Foundation
-  // MFT decryptor. Nothing will be rendered but it is sufficient for initial
-  // testing. Remove this block after fixing the bug.
-  {
-    output_samples[0].pSample->Release();
-    output_samples[0].pSample = nullptr;
-  }
+  // Convert the Media Foundation sample to a DecoderBuffer.
+  scoped_refptr<DecoderBuffer> encrypted_buffer;
+  RETURN_IF_FAILED(GenerateDecoderBufferFromSample(
+      sample_.Detach(), &key_id_guid, &encrypted_buffer));
+  DVLOG_FUNC(3) << "encrypted_buffer=" +
+                       encrypted_buffer->AsHumanReadableString(true);
+
+  // Decrypt the protected content.
+  Decryptor::Status decryptor_status = Decryptor::kError;
+
+  // TODO(crbug.com/1442373): We may remove the tracking code of stream type
+  // if two decryptors get created for audio and video respectively.
+  CHECK(stream_type_ != StreamType::kUnknown);
+  Decryptor::StreamType stream_type = stream_type_ == StreamType::kVideo
+                                          ? Decryptor::kVideo
+                                          : Decryptor::kAudio;
+  scoped_refptr<DecoderBuffer> decrypted_buffer;
+  bool is_decrypt_completed = false;
+  aes_decryptor_->Decrypt(
+      stream_type, encrypted_buffer,
+      base::BindOnce(
+          [](Decryptor::Status* status_copy,
+             scoped_refptr<DecoderBuffer>* buffer_copy,
+             bool* is_decrypt_completed, Decryptor::Status status,
+             scoped_refptr<DecoderBuffer> buffer) {
+            *status_copy = status;
+            *buffer_copy = std::move(buffer);
+            *is_decrypt_completed = true;
+          },
+          &decryptor_status, &decrypted_buffer, &is_decrypt_completed));
+
+  // Ensure the decryption is done synchronously.
+  CHECK(is_decrypt_completed);
+
+  // Convert the DecoderBuffer back to a Media Foundation sample.
+  ComPtr<IMFSample> sample_decrypted = nullptr;
+  GUID last_key_id = GUID_NULL;
+  RETURN_IF_FAILED(
+      GenerateSampleFromDecoderBuffer(decrypted_buffer.get(), &sample_decrypted,
+                                      &last_key_id, base::NullCallback()));
+  DVLOG_FUNC(3) << "decrypted_buffer=" +
+                       decrypted_buffer->AsHumanReadableString(true);
+
+  output_samples[0].pSample = sample_decrypted.Detach();
 
   return S_OK;
 }
diff --git a/media/gpu/chromeos/video_decoder_pipeline.h b/media/gpu/chromeos/video_decoder_pipeline.h
index 1243154..f1a03ecc 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.h
+++ b/media/gpu/chromeos/video_decoder_pipeline.h
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "build/build_config.h"
diff --git a/media/gpu/v4l2/generic_v4l2_device.cc b/media/gpu/v4l2/generic_v4l2_device.cc
index fe0e251..f35a8ea 100644
--- a/media/gpu/v4l2/generic_v4l2_device.cc
+++ b/media/gpu/v4l2/generic_v4l2_device.cc
@@ -29,6 +29,7 @@
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/macros.h"
 #include "media/gpu/v4l2/generic_v4l2_device.h"
+#include "media/gpu/v4l2/v4l2_utils.h"
 #include "ui/gfx/native_pixmap.h"
 #include "ui/gfx/native_pixmap_handle.h"
 #include "ui/gl/egl_util.h"
@@ -312,8 +313,8 @@
       continue;
     }
 
-    std::vector<uint32_t> pixelformats =
-        EnumerateSupportedPixelformats(buf_type);
+    const auto pixelformats = EnumerateSupportedPixFmts(
+        base::BindRepeating(&V4L2Device::Ioctl, this), buf_type);
 
     supported_pixelformats.insert(supported_pixelformats.end(),
                                   pixelformats.begin(), pixelformats.end());
@@ -470,9 +471,9 @@
   for (const auto& path : candidate_paths) {
     if (!OpenDevicePath(path, type))
       continue;
+    const auto supported_pixelformats = EnumerateSupportedPixFmts(
+        base::BindRepeating(&V4L2Device::Ioctl, this), buf_type);
 
-    const auto& supported_pixelformats =
-        EnumerateSupportedPixelformats(buf_type);
     if (!supported_pixelformats.empty()) {
       DVLOGF(3) << "Found device: " << path;
       devices.push_back(std::make_pair(path, supported_pixelformats));
diff --git a/media/gpu/v4l2/v4l2_device.cc b/media/gpu/v4l2/v4l2_device.cc
index e59918b57..cef48061 100644
--- a/media/gpu/v4l2/v4l2_device.cc
+++ b/media/gpu/v4l2/v4l2_device.cc
@@ -1945,32 +1945,16 @@
   return rate_control_mode;
 }
 
-std::vector<uint32_t> V4L2Device::EnumerateSupportedPixelformats(
-    v4l2_buf_type buf_type) {
-  std::vector<uint32_t> pixelformats;
-
-  v4l2_fmtdesc fmtdesc;
-  memset(&fmtdesc, 0, sizeof(fmtdesc));
-  fmtdesc.type = buf_type;
-
-  for (; Ioctl(VIDIOC_ENUM_FMT, &fmtdesc) == 0; ++fmtdesc.index) {
-    DVLOGF(3) << "Found " << FourccToString(fmtdesc.pixelformat) << " ("
-              << fmtdesc.description << ")";
-    pixelformats.push_back(fmtdesc.pixelformat);
-  }
-
-  return pixelformats;
-}
-
 VideoDecodeAccelerator::SupportedProfiles
 V4L2Device::EnumerateSupportedDecodeProfiles(const size_t num_formats,
                                              const uint32_t pixelformats[]) {
   VideoDecodeAccelerator::SupportedProfiles profiles;
 
-  const auto& supported_pixelformats =
-      EnumerateSupportedPixelformats(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+  const auto v4l2_codecs_as_pix_fmts =
+      EnumerateSupportedPixFmts(base::BindRepeating(&V4L2Device::Ioctl, this),
+                                V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
 
-  for (uint32_t pixelformat : supported_pixelformats) {
+  for (uint32_t pixelformat : v4l2_codecs_as_pix_fmts) {
     if (std::find(pixelformats, pixelformats + num_formats, pixelformat) ==
         pixelformats + num_formats)
       continue;
@@ -2006,10 +1990,11 @@
 V4L2Device::EnumerateSupportedEncodeProfiles() {
   VideoEncodeAccelerator::SupportedProfiles profiles;
 
-  const auto& supported_pixelformats =
-      EnumerateSupportedPixelformats(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+  const auto v4l2_codecs_as_pix_fmts =
+      EnumerateSupportedPixFmts(base::BindRepeating(&V4L2Device::Ioctl, this),
+                                V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
 
-  for (const auto& pixelformat : supported_pixelformats) {
+  for (const auto& pixelformat : v4l2_codecs_as_pix_fmts) {
     VideoEncodeAccelerator::SupportedProfile profile;
     profile.max_framerate_numerator = 30;
     profile.max_framerate_denominator = 1;
diff --git a/media/gpu/v4l2/v4l2_device.h b/media/gpu/v4l2/v4l2_device.h
index 1a82aba..7cdbc62 100644
--- a/media/gpu/v4l2/v4l2_device.h
+++ b/media/gpu/v4l2/v4l2_device.h
@@ -807,8 +807,6 @@
   VideoEncodeAccelerator::SupportedRateControlMode
   GetSupportedRateControlMode();
 
-  std::vector<uint32_t> EnumerateSupportedPixelformats(v4l2_buf_type buf_type);
-
   // NOTE: The below methods to query capabilities have a side effect of
   // closing the previously-open device, if any, and should not be called after
   // Open().
diff --git a/media/gpu/v4l2/v4l2_utils.cc b/media/gpu/v4l2/v4l2_utils.cc
index 94a5ba4..5a501e2 100644
--- a/media/gpu/v4l2/v4l2_utils.cc
+++ b/media/gpu/v4l2/v4l2_utils.cc
@@ -55,6 +55,9 @@
 
 namespace media {
 
+// Numerical value of ioctl() OK return value;
+constexpr int kIoctlOk = 0;
+
 const char* V4L2MemoryToString(const v4l2_memory memory) {
   switch (memory) {
     case V4L2_MEMORY_MMAP:
@@ -252,7 +255,6 @@
   }
   const auto profile_cid = kV4L2CodecPixFmtToProfileCID.at(codec_as_pix_fmt);
 
-  constexpr int kIoctlOk = 0;
   v4l2_queryctrl query_ctrl = {.id = static_cast<__u32>(profile_cid)};
   if (ioctl_cb.Run(VIDIOC_QUERYCTRL, &query_ctrl) != kIoctlOk) {
     // This happens for example for VP8 on Hana MTK8173, or for HEVC on Trogdor
@@ -293,4 +295,25 @@
   return profiles;
 }
 
+std::vector<uint32_t> EnumerateSupportedPixFmts(
+    base::RepeatingCallback<int(int, void*)> ioctl_cb,
+    v4l2_buf_type buf_type) {
+  DCHECK(buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ||
+         buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+  std::vector<v4l2_enum_type> pix_fmts;
+  v4l2_fmtdesc fmtdesc = {.type = buf_type};
+  for (; ioctl_cb.Run(VIDIOC_ENUM_FMT, &fmtdesc) == kIoctlOk; ++fmtdesc.index) {
+    DVLOGF(4) << "Enumerated "
+              << (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
+                      ? "codec: "
+                      : "pixel format: ")
+              << FourccToString(fmtdesc.pixelformat) << " ("
+              << fmtdesc.description << ")";
+    pix_fmts.push_back(fmtdesc.pixelformat);
+  }
+
+  return pix_fmts;
+}
+
 }  // namespace media
diff --git a/media/gpu/v4l2/v4l2_utils.h b/media/gpu/v4l2/v4l2_utils.h
index e59c2c77..9fa8125f 100644
--- a/media/gpu/v4l2/v4l2_utils.h
+++ b/media/gpu/v4l2/v4l2_utils.h
@@ -42,6 +42,13 @@
     base::RepeatingCallback<int(int, void*)> ioctl_cb,
     uint32_t codec_as_pix_fmt);
 
+// Enumerates all supported pixel formats for a given device (accessed via
+// |ioctl_cb|) and for |buf_type|; these will be the supported video codecs
+// (e.g. V4L2_PIX_FMT_VP9) for V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE.
+std::vector<uint32_t> EnumerateSupportedPixFmts(
+    base::RepeatingCallback<int(int, void*)> ioctl_cb,
+    v4l2_buf_type buf_type);
+
 }  // namespace media
 
 #endif  // MEDIA_GPU_V4L2_V4L2_UTILS_H_
diff --git a/media/gpu/v4l2/v4l2_video_decoder.cc b/media/gpu/v4l2/v4l2_video_decoder.cc
index 6ba2cd9d..39e8876 100644
--- a/media/gpu/v4l2/v4l2_video_decoder.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder.cc
@@ -27,6 +27,7 @@
 #include "media/gpu/gpu_video_decode_accelerator_helpers.h"
 #include "media/gpu/macros.h"
 #include "media/gpu/v4l2/v4l2_status.h"
+#include "media/gpu/v4l2/v4l2_utils.h"
 #include "media/gpu/v4l2/v4l2_video_decoder_backend_stateful.h"
 #include "media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h"
 
@@ -389,9 +390,10 @@
   DCHECK_EQ(state_, State::kInitialized);
 
   // Check if the format is supported.
-  std::vector<uint32_t> formats = device_->EnumerateSupportedPixelformats(
+  const auto v4l2_codecs_as_pix_fmts = EnumerateSupportedPixFmts(
+      base::BindRepeating(&V4L2Device::Ioctl, device_),
       V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
-  if (!base::Contains(formats, fourcc)) {
+  if (!base::Contains(v4l2_codecs_as_pix_fmts, fourcc)) {
     DVLOGF(1) << FourccToString(fourcc) << " not recognised, skipping...";
     return false;
   }
@@ -426,10 +428,12 @@
   DVLOGF(3) << "size: " << size.ToString()
             << ", visible_rect: " << visible_rect.ToString();
 
-  // Get the supported output formats and their corresponding negotiated sizes.
+  const auto v4l2_pix_fmts = EnumerateSupportedPixFmts(
+      base::BindRepeating(&V4L2Device::Ioctl, device_),
+      V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
   std::vector<PixelLayoutCandidate> candidates;
-  for (const uint32_t& pixfmt : device_->EnumerateSupportedPixelformats(
-           V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) {
+  for (const uint32_t& pixfmt : v4l2_pix_fmts) {
     const auto candidate = Fourcc::FromV4L2PixFmt(pixfmt);
     if (!candidate) {
       DVLOGF(1) << FourccToString(pixfmt) << " is not recognised, skipping...";
diff --git a/media/gpu/windows/d3d11_h264_accelerator.cc b/media/gpu/windows/d3d11_h264_accelerator.cc
index fee4ded..0ed257ee 100644
--- a/media/gpu/windows/d3d11_h264_accelerator.cc
+++ b/media/gpu/windows/d3d11_h264_accelerator.cc
@@ -30,18 +30,6 @@
 
 using H264DecoderStatus = H264Decoder::H264Accelerator::Status;
 
-// Converts SubsampleEntry to D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK.
-void AppendSubsamples(
-    const std::vector<SubsampleEntry>& from,
-    std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK>* to) {
-  for (const auto& from_entry : from) {
-    D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK subsample = {};
-    subsample.ClearSize = from_entry.clear_bytes;
-    subsample.EncryptedSize = from_entry.cypher_bytes;
-    to->push_back(subsample);
-  }
-}
-
 }  // namespace
 
 class D3D11H264Picture : public H264Picture {
@@ -92,13 +80,6 @@
     const H264Picture::Vector& ref_pic_listb0,
     const H264Picture::Vector& ref_pic_listb1,
     scoped_refptr<H264Picture> pic) {
-  const bool is_encrypted = pic->decrypt_config();
-  if (is_encrypted) {
-    RecordFailure("Cannot find decrypt context for the frame.",
-                  D3D11Status::Codes::kCryptoConfigFailed);
-    return H264DecoderStatus::kFail;
-  }
-
   HRESULT hr;
   for (;;) {
     D3D11H264Picture* d3d11_pic = pic->AsD3D11H264Picture();
@@ -331,7 +312,6 @@
     const uint8_t* data,
     size_t size,
     const std::vector<SubsampleEntry>& subsamples) {
-  const bool is_encrypted = pic->decrypt_config();
   DXVA_PicParams_H264 pic_param = {};
   FillPicParamsWithConstants(&pic_param);
 
@@ -431,30 +411,6 @@
   size_t remaining_bitstream = out_bitstream_size;
   size_t start_location = 0;
 
-  if (is_encrypted) {
-    // For now, the entire frame has to fit into the bitstream buffer. This way
-    // the subsample ClearSize adjustment below should work.
-    if (bitstream_buffer_size_ < remaining_bitstream) {
-      RecordFailure("Input slice NALU (" + std::to_string(remaining_bitstream) +
-                        ") too big to fit in the bistream buffer (" +
-                        base::NumberToString(bitstream_buffer_size_) + ").",
-                    D3D11Status::Codes::kBitstreamBufferSliceTooBig);
-      return H264DecoderStatus::kFail;
-    }
-
-    AppendSubsamples(subsamples, &subsamples_);
-    if (!subsamples.empty()) {
-      // 3 added to clear bytes because a start code is prepended to the slice
-      // NALU.
-      // TODO(rkuroiwa): This should be done right after the start code is
-      // written to the buffer, but currently the start code is written in the
-      // loop (which is not the right place, there's only one slice NALU passed
-      // into this function) and it's not easy to identify where the subsample
-      // starts in the buffer.
-      subsamples_[subsamples_.size() - subsamples.size()].ClearSize += 3;
-    }
-  }
-
   while (remaining_bitstream > 0) {
     if (bitstream_buffer_size_ < remaining_bitstream &&
         slice_info_.size() > 0) {
@@ -554,24 +510,12 @@
   buffers[3].DataOffset = 0;
   buffers[3].DataSize = current_offset_;
 
-  if (!frame_iv_.empty()) {
-    buffers[3].pIV = frame_iv_.data();
-    buffers[3].IVSize = frame_iv_.size();
-    // Subsmaples matter iff there is IV, for decryption.
-    if (!subsamples_.empty()) {
-      buffers[3].pSubSampleMappingBlock = subsamples_.data();
-      buffers[3].SubSampleMappingCount = subsamples_.size();
-    }
-  }
-
   hr = video_context_->SubmitDecoderBuffers(video_decoder_.Get(),
                                             std::size(buffers), buffers);
   current_offset_ = 0;
   slice_info_.clear();
   bitstream_buffer_bytes_ = nullptr;
   bitstream_buffer_size_ = 0;
-  frame_iv_.clear();
-  subsamples_.clear();
   if (!SUCCEEDED(hr)) {
     RecordFailure("SubmitDecoderBuffers failed",
                   D3D11Status::Codes::kSubmitDecoderBuffersFailed, hr);
diff --git a/media/gpu/windows/d3d11_h264_accelerator.h b/media/gpu/windows/d3d11_h264_accelerator.h
index eccc458..73b0183 100644
--- a/media/gpu/windows/d3d11_h264_accelerator.h
+++ b/media/gpu/windows/d3d11_h264_accelerator.h
@@ -102,12 +102,6 @@
   size_t current_offset_ = 0;
   size_t bitstream_buffer_size_ = 0;
   raw_ptr<uint8_t, AllowPtrArithmetic> bitstream_buffer_bytes_ = nullptr;
-
-  // This contains the subsamples (clear and encrypted) of the slice data
-  // in D3D11_VIDEO_DECODER_BUFFER_BITSTREAM buffer.
-  std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK> subsamples_;
-  // IV for the current frame.
-  std::vector<uint8_t> frame_iv_;
 };
 
 }  // namespace media
diff --git a/media/gpu/windows/d3d11_h265_accelerator.cc b/media/gpu/windows/d3d11_h265_accelerator.cc
index 4a2b8d6..ed85695 100644
--- a/media/gpu/windows/d3d11_h265_accelerator.cc
+++ b/media/gpu/windows/d3d11_h265_accelerator.cc
@@ -31,18 +31,6 @@
 
 using H265DecoderStatus = H265Decoder::H265Accelerator::Status;
 
-// Converts SubsampleEntry to D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK.
-void AppendSubsamples(
-    const std::vector<SubsampleEntry>& from,
-    std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK>* to) {
-  for (const auto& from_entry : from) {
-    D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK subsample = {};
-    subsample.ClearSize = from_entry.clear_bytes;
-    subsample.EncryptedSize = from_entry.cypher_bytes;
-    to->push_back(subsample);
-  }
-}
-
 }  // namespace
 
 class D3D11H265Picture : public H265Picture {
@@ -101,13 +89,6 @@
     const H265Picture::Vector& ref_pic_set_st_curr_after,
     const H265Picture::Vector& ref_pic_set_st_curr_before,
     scoped_refptr<H265Picture> pic) {
-  const bool is_encrypted = pic->decrypt_config();
-  if (is_encrypted) {
-    RecordFailure("Cannot find decrypt context for the frame.",
-                  D3D11Status::Codes::kCryptoConfigFailed);
-    return H265DecoderStatus::kFail;
-  }
-
   HRESULT hr;
   for (;;) {
     D3D11H265Picture* d3d11_pic = pic->AsD3D11H265Picture();
@@ -631,25 +612,6 @@
   size_t remaining_bitstream = out_bitstream_size;
   size_t start_location = 0;
 
-  if (pic->decrypt_config()) {
-    // For now, the entire frame has to fit into the bitstream buffer. This way
-    // the subsample ClearSize adjustment below should work.
-    if (bitstream_buffer_size_ < remaining_bitstream) {
-      RecordFailure("Input slice NALU (" +
-                        base::NumberToString(remaining_bitstream) +
-                        ") too big to fit in the bistream buffer (" +
-                        base::NumberToString(bitstream_buffer_size_) + ").",
-                    D3D11Status::Codes::kBitstreamBufferSliceTooBig);
-      return H265DecoderStatus::kFail;
-    }
-
-    AppendSubsamples(subsamples, &subsamples_);
-    if (!subsamples.empty()) {
-      // Follow same logic as D3D11H264Accelerator.
-      subsamples_[subsamples_.size() - subsamples.size()].ClearSize += 3;
-    }
-  }
-
   while (remaining_bitstream > 0) {
     if (bitstream_buffer_size_ < remaining_bitstream &&
         slice_info_.size() > 0) {
@@ -759,16 +721,6 @@
     buffers[3].DataSize = sizeof(DXVA_Qmatrix_HEVC);
   }
 
-  if (!frame_iv_.empty()) {
-    buffers[2].pIV = frame_iv_.data();
-    buffers[2].IVSize = frame_iv_.size();
-    // Subsmaples matter iff there is IV, for decryption.
-    if (!subsamples_.empty()) {
-      buffers[2].pSubSampleMappingBlock = subsamples_.data();
-      buffers[2].SubSampleMappingCount = subsamples_.size();
-    }
-  }
-
   hr = video_context_->SubmitDecoderBuffers(
       video_decoder_.Get(),
       use_scaling_lists_ ? std::size(buffers) : std::size(buffers) - 1,
@@ -777,8 +729,6 @@
   slice_info_.clear();
   bitstream_buffer_bytes_ = nullptr;
   bitstream_buffer_size_ = 0;
-  frame_iv_.clear();
-  subsamples_.clear();
   if (!SUCCEEDED(hr)) {
     RecordFailure("SubmitDecoderBuffers failed",
                   D3D11Status::Codes::kSubmitDecoderBuffersFailed, hr);
diff --git a/media/gpu/windows/d3d11_h265_accelerator.h b/media/gpu/windows/d3d11_h265_accelerator.h
index ac93cbc..607a0a98 100644
--- a/media/gpu/windows/d3d11_h265_accelerator.h
+++ b/media/gpu/windows/d3d11_h265_accelerator.h
@@ -197,12 +197,6 @@
   // For HEVC this number needs to be larger than 1 and different
   // in each call to Execute().
   int current_status_report_feedback_num_ = 1;
-
-  // This contains the subsamples (clear and encrypted) of the slice data
-  // in D3D11_VIDEO_DECODER_BUFFER_BITSTREAM buffer.
-  std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK> subsamples_;
-  // IV for the current frame.
-  std::vector<uint8_t> frame_iv_;
 };
 
 }  // namespace media
diff --git a/media/gpu/windows/d3d11_vp9_accelerator.cc b/media/gpu/windows/d3d11_vp9_accelerator.cc
index 0e34d1d..7538b93 100644
--- a/media/gpu/windows/d3d11_vp9_accelerator.cc
+++ b/media/gpu/windows/d3d11_vp9_accelerator.cc
@@ -25,17 +25,6 @@
     }                                               \
   } while (0)
 
-std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK>
-CreateSubsampleMappingBlock(const std::vector<SubsampleEntry>& from) {
-  std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK> to(from.size());
-  for (const auto& entry : from) {
-    D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK subsample = {
-        .ClearSize = entry.clear_bytes, .EncryptedSize = entry.cypher_bytes};
-    to.push_back(subsample);
-  }
-  return to;
-}
-
 D3D11VP9Accelerator::D3D11VP9Accelerator(
     D3D11VideoDecoderClient* client,
     MediaLog* media_log,
@@ -57,15 +46,6 @@
 }
 
 bool D3D11VP9Accelerator::BeginFrame(const D3D11VP9Picture& pic) {
-  const bool is_encrypted = pic.decrypt_config();
-  if (is_encrypted) {
-    RecordFailure(
-        "crypto_config: Cannot find the decrypt context for the "
-        "frame.",
-        D3D11Status::Codes::kCryptoConfigFailed);
-    return false;
-  }
-
   HRESULT hr;
   do {
     ID3D11VideoDecoderOutputView* output_view = nullptr;
@@ -322,18 +302,6 @@
     buffers[2].DataOffset = 0;
     buffers[2].DataSize = copy_size;
 
-    const DecryptConfig* config = pic.decrypt_config();
-    if (config) {
-      buffers[2].pIV = const_cast<char*>(config->iv().data());
-      buffers[2].IVSize = config->iv().size();
-      // Subsamples matter iff there is IV, for decryption.
-      if (!config->subsamples().empty()) {
-        buffers[2].pSubSampleMappingBlock =
-            CreateSubsampleMappingBlock(config->subsamples()).data();
-        buffers[2].SubSampleMappingCount = config->subsamples().size();
-      }
-    }
-
     RETURN_ON_HR_FAILURE(SubmitDecoderBuffers,
                          video_context_->SubmitDecoderBuffers(
                              video_decoder_.Get(), buffers_count, buffers),
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
index af6b7c5..cdc685c 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -360,10 +360,12 @@
   EncodeOutput(uint32_t size,
                bool key_frame,
                base::TimeDelta timestamp,
-               int temporal_id = 0)
+               int temporal_id,
+               absl::optional<gfx::ColorSpace> color_space)
       : keyframe(key_frame),
         capture_timestamp(timestamp),
         temporal_layer_id(temporal_id),
+        color_space(color_space),
         data_(size) {}
 
   EncodeOutput(const EncodeOutput&) = delete;
@@ -378,6 +380,7 @@
   const base::TimeDelta capture_timestamp;
   const int temporal_layer_id;
   absl::optional<int32_t> frame_qp;
+  const absl::optional<gfx::ColorSpace> color_space;
 
  private:
   std::vector<uint8_t> data_;
@@ -779,6 +782,9 @@
   if (temporal_scalable_coding()) {
     md.h264.emplace().temporal_idx = encode_output->temporal_layer_id;
   }
+  if (encode_output->color_space) {
+    md.encoded_color_space = *encode_output->color_space;
+  }
 
   client_->BitstreamBufferReady(buffer_ref->id, md);
 }
@@ -1243,6 +1249,7 @@
       RETURN_ON_HR_FAILURE(hr, "Couldn't set input sample attribute QP", hr);
     }
 
+    output_color_spaces_.push_back(encoder_color_space_);
     has_prepared_input_sample_ = true;
   }
 
@@ -1726,13 +1733,17 @@
   }
   DVLOG(3) << "Encoded data with size:" << size << " keyframe " << keyframe;
 
+  DCHECK(!output_color_spaces_.empty());
+  auto output_cs = output_color_spaces_.front();
+  output_color_spaces_.pop_front();
+
   // If no bit stream buffer presents, queue the output first.
   if (bitstream_buffer_queue_.empty()) {
     DVLOG(3) << "No bitstream buffers.";
 
     // We need to copy the output so that encoding can continue.
-    auto encode_output =
-        std::make_unique<EncodeOutput>(size, keyframe, timestamp, temporal_id);
+    auto encode_output = std::make_unique<EncodeOutput>(
+        size, keyframe, timestamp, temporal_id, output_cs);
     {
       MediaBufferScopedPointer scoped_buffer(output_buffer.Get());
       memcpy(encode_output->memory(), scoped_buffer.get(), size);
@@ -1775,9 +1786,8 @@
       md.h265.emplace().temporal_idx = temporal_id;
     }
   }
-
-  if (encoder_color_space_) {
-    md.encoded_color_space = *encoder_color_space_;
+  if (output_cs) {
+    md.encoded_color_space = *output_cs;
   }
 
   client_->BitstreamBufferReady(buffer_ref->id, md);
@@ -1822,6 +1832,8 @@
     }
     case METransformDrainComplete: {
       DCHECK(pending_input_queue_.empty());
+      DCHECK(encoder_output_queue_.empty());
+      DCHECK(output_color_spaces_.empty());
       DCHECK_EQ(state_, kFlushing);
       auto hr = encoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
       if (FAILED(hr)) {
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
index 01b81ec..3d674e4 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
@@ -285,8 +285,10 @@
   // Bitrate controller for CBR encoding.
   std::unique_ptr<VideoRateControlWrapper> rate_ctrl_;
 
-  // Color space of the first frame sent to Encode().
+  // Color space of the first frame sent to Encode(). Every input gets an entry
+  // in `output_color_spaces_`, new outputs take the color space from the front.
   absl::optional<gfx::ColorSpace> encoder_color_space_;
+  base::circular_deque<absl::optional<gfx::ColorSpace>> output_color_spaces_;
 
   // Declared last to ensure that all weak pointers are invalidated before
   // other destructors run.
diff --git a/media/test/data/README.md b/media/test/data/README.md
index b6ce46e..950df3b6 100644
--- a/media/test/data/README.md
+++ b/media/test/data/README.md
@@ -183,6 +183,19 @@
   --BitstreamFile=bbb_chroma_qp_offset_lists.vvc
 ```
 
+#### bbb_scaling_lists.vvc
+VVC stream generated with VTM that is used to verify scaling list parsing.
+Created with VTM and ffmpeg:
+```
+ffmpeg -i bbb-320x240-2video-2audio.mp4 bbb.y4m
+ffmpeg -i bbb.y4m -vf scale=1920x1080 bbb_1920x1080.yuv
+EncoderApp --CTUSize=128 -c sample_scaling_list.cfg -f 8 \
+  --InputFile=bbb_1920x1080.yuv \
+  --BitstreamFile=bbb_scaling_lists.vvc
+```
+Please be noted sample_scaling_list.cfg is the file with the same name from VTM
+sample configure.
+
 ### AV1
 
 Unless noted otherwise, the codec string is `av01.0.04M.08` for 8-bit files,
diff --git a/media/test/data/bbb_scaling_lists.vvc b/media/test/data/bbb_scaling_lists.vvc
new file mode 100644
index 0000000..ee698ed7
--- /dev/null
+++ b/media/test/data/bbb_scaling_lists.vvc
Binary files differ
diff --git a/media/test/media_bundle_data.filelist b/media/test/media_bundle_data.filelist
index 36e4c042..0bde58b6 100644
--- a/media/test/media_bundle_data.filelist
+++ b/media/test/media_bundle_data.filelist
@@ -41,6 +41,7 @@
 data/bbb_9tiles.vvc
 data/bbb_9tiles_18slices.vvc
 data/bbb_chroma_qp_offset_lists.vvc
+data/bbb_scaling_lists.vvc
 data/bear-1280x720-a_frag-cenc-key_rotation.mp4
 data/bear-1280x720-a_frag-cenc.mp4
 data/bear-1280x720-a_frag-cenc_clear-all.mp4
diff --git a/media/unit_tests_bundle_data.filelist b/media/unit_tests_bundle_data.filelist
index d80783e..53c09ac 100644
--- a/media/unit_tests_bundle_data.filelist
+++ b/media/unit_tests_bundle_data.filelist
@@ -38,6 +38,7 @@
 //media/test/data/bbb_9tiles.vvc
 //media/test/data/bbb_9tiles_18slices.vvc
 //media/test/data/bbb_chroma_qp_offset_lists.vvc
+//media/test/data/bbb_scaling_lists.vvc
 //media/test/data/bear-1280x720-a_frag-cenc-key_rotation.mp4
 //media/test/data/bear-1280x720-a_frag-cenc.mp4
 //media/test/data/bear-1280x720-a_frag-cenc_clear-all.mp4
diff --git a/media/video/h266_parser.cc b/media/video/h266_parser.cc
index d2f9846..2b0525d 100644
--- a/media/video/h266_parser.cc
+++ b/media/video/h266_parser.cc
@@ -22,6 +22,27 @@
 
 namespace media {
 
+namespace {
+// Equation 24. kDiagScanOrderMxM[i][0] is the horizontal component, and
+// kDiagScanOrderMxM[i][1] is the vertical component.
+constexpr uint8_t kDiagScanOrder8x8[64][2] = {
+    {0, 0}, {0, 1}, {1, 0}, {0, 2}, {1, 1}, {2, 0}, {0, 3}, {1, 2},
+    {2, 1}, {3, 0}, {0, 4}, {1, 3}, {2, 2}, {3, 1}, {4, 0}, {0, 5},
+    {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}, {0, 6}, {1, 5}, {2, 4},
+    {3, 3}, {4, 2}, {5, 1}, {6, 0}, {0, 7}, {1, 6}, {2, 5}, {3, 4},
+    {4, 3}, {5, 2}, {6, 1}, {7, 0}, {1, 7}, {2, 6}, {3, 5}, {4, 4},
+    {5, 3}, {6, 2}, {7, 1}, {2, 7}, {3, 6}, {4, 5}, {5, 4}, {6, 3},
+    {7, 2}, {3, 7}, {4, 6}, {5, 5}, {6, 4}, {7, 3}, {4, 7}, {5, 6},
+    {6, 5}, {7, 4}, {5, 7}, {6, 6}, {7, 5}, {6, 7}, {7, 6}, {7, 7}};
+
+constexpr uint8_t kDiagScanOrder4x4[16][2] = {
+    {0, 0}, {0, 1}, {1, 0}, {0, 2}, {1, 1}, {2, 0}, {0, 3}, {1, 2},
+    {2, 1}, {3, 0}, {1, 3}, {2, 2}, {3, 1}, {2, 3}, {3, 2}, {3, 3}};
+
+constexpr uint8_t kDiagScanOrder2x2[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
+
+}  // namespace
+
 H266ProfileTierLevel::H266ProfileTierLevel() {
   memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
 }
@@ -58,6 +79,35 @@
   memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
 }
 
+H266APS::H266APS(int aps_type) : aps_params_type(aps_type) {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+  switch (aps_type) {
+    case 0:
+      data.emplace<H266AlfData>();
+      break;
+    case 1:
+      data.emplace<H266LmcsData>();
+      break;
+    case 2:
+      data.emplace<H266ScalingListData>();
+      break;
+  }
+}
+
+H266APS::~H266APS() = default;
+
+H266ScalingListData::H266ScalingListData() {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+}
+
+H266AlfData::H266AlfData() {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+}
+
+H266LmcsData::H266LmcsData() {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+}
+
 H266Parser::H266Parser() = default;
 
 H266Parser::~H266Parser() = default;
@@ -2443,6 +2493,400 @@
   return kOk;
 }
 
+// 7.3.2.6 Adaptation parameter set
+// APS conveys slice level information which may be shared by
+// multiple slices of a picture or slices of different pictures.
+// There might be many APSs for a bitstream, and would be typically
+// updated very frequently.
+H266Parser::Result H266Parser::ParseAPS(const H266NALU& nalu,
+                                        int* aps_id,
+                                        H266APS::ParamType* type) {
+  DCHECK(aps_id);
+
+  int aps_type;
+  READ_BITS_OR_RETURN(3, &aps_type);
+  IN_RANGE_OR_RETURN(aps_type, 0, 2);
+  std::unique_ptr<H266APS> aps = std::make_unique<H266APS>(aps_type);
+
+  aps->nal_unit_type = nalu.nal_unit_type;
+  aps->nuh_layer_id = nalu.nuh_layer_id;
+  aps->aps_params_type = aps_type;
+
+  READ_BITS_OR_RETURN(5, &aps->aps_adaptation_parameter_set_id);
+  if (aps->aps_params_type == H266APS::kAlf ||
+      aps->aps_params_type == H266APS::kScalingList) {
+    IN_RANGE_OR_RETURN(aps->aps_adaptation_parameter_set_id, 0, 7);
+  } else if (aps->aps_params_type == H266APS::kLmcs) {
+    IN_RANGE_OR_RETURN(aps->aps_adaptation_parameter_set_id, 0, 3);
+  }
+  READ_BOOL_OR_RETURN(&aps->aps_chroma_present_flag);
+
+  if (aps->aps_params_type == H266APS::kAlf) {
+    // 7.3.2.18: Adaptive loop filter
+    H266AlfData* alf_data = &std::get<H266AlfData>(aps->data);
+    READ_BOOL_OR_RETURN(&alf_data->alf_luma_filter_signal_flag);
+    if (aps->aps_chroma_present_flag) {
+      READ_BOOL_OR_RETURN(&alf_data->alf_chroma_filter_signal_flag);
+      READ_BOOL_OR_RETURN(&alf_data->alf_cc_cb_filter_signal_flag);
+      READ_BOOL_OR_RETURN(&alf_data->alf_cc_cr_filter_signal_flag);
+    } else {
+      alf_data->alf_chroma_filter_signal_flag = 0;
+      alf_data->alf_cc_cb_filter_signal_flag = 0;
+      alf_data->alf_cc_cr_filter_signal_flag = 0;
+    }
+    // 7.4.3.18: at least one of above signal flag should be 1.
+    TRUE_OR_RETURN(alf_data->alf_luma_filter_signal_flag ||
+                   alf_data->alf_chroma_filter_signal_flag ||
+                   alf_data->alf_cc_cb_filter_signal_flag ||
+                   alf_data->alf_cc_cr_filter_signal_flag);
+
+    if (alf_data->alf_luma_filter_signal_flag) {
+      READ_BOOL_OR_RETURN(&alf_data->alf_luma_clip_flag);
+      READ_UE_OR_RETURN(&alf_data->alf_luma_num_filters_signalled_minus1);
+      IN_RANGE_OR_RETURN(alf_data->alf_luma_num_filters_signalled_minus1, 0,
+                         kNumAlfFilters - 1);
+
+      if (alf_data->alf_luma_num_filters_signalled_minus1 > 0) {
+        int signaled_filters_len = base::bits::Log2Ceiling(
+            alf_data->alf_luma_num_filters_signalled_minus1 + 1);
+        for (int filter_idx = 0; filter_idx < kNumAlfFilters; filter_idx++) {
+          READ_BITS_OR_RETURN(signaled_filters_len,
+                              &std::get<H266AlfData>(aps->data)
+                                   .alf_luma_coeff_delta_idx[filter_idx]);
+          IN_RANGE_OR_RETURN(alf_data->alf_luma_coeff_delta_idx[filter_idx], 0,
+                             alf_data->alf_luma_num_filters_signalled_minus1);
+        }
+      }
+
+      for (int sf_idx = 0;
+           sf_idx <= alf_data->alf_luma_num_filters_signalled_minus1;
+           sf_idx++) {
+        for (int j = 0; j < 12; j++) {
+          READ_UE_OR_RETURN(
+              &std::get<H266AlfData>(aps->data).alf_luma_coeff_abs[sf_idx][j]);
+          IN_RANGE_OR_RETURN(alf_data->alf_luma_coeff_abs[sf_idx][j], 0, 128);
+          if (alf_data->alf_luma_coeff_abs[sf_idx][j]) {
+            // alf_luma_coeff_sign[sf_idx][j] equals to 0 indicates a positive
+            // value and otherwise a negative value.
+            READ_BOOL_OR_RETURN(&std::get<H266AlfData>(aps->data)
+                                     .alf_luma_coeff_sign[sf_idx][j]);
+          } else {
+            alf_data->alf_luma_coeff_sign[sf_idx][j] = 0;
+          }
+        }
+      }
+
+      if (alf_data->alf_luma_clip_flag) {
+        for (int sf_idx = 0;
+             sf_idx <= alf_data->alf_luma_num_filters_signalled_minus1;
+             sf_idx++) {
+          for (int j = 0; j < 12; j++) {
+            READ_BITS_OR_RETURN(
+                2,
+                &std::get<H266AlfData>(aps->data).alf_luma_clip_idx[sf_idx][j]);
+          }
+        }
+      }
+    }
+
+    if (alf_data->alf_chroma_filter_signal_flag) {
+      READ_BOOL_OR_RETURN(
+          &std::get<H266AlfData>(aps->data).alf_chroma_clip_flag);
+      READ_UE_OR_RETURN(
+          &std::get<H266AlfData>(aps->data).alf_chroma_num_alt_filters_minus1);
+      IN_RANGE_OR_RETURN(alf_data->alf_chroma_num_alt_filters_minus1, 0, 7);
+
+      for (int alt_idx = 0;
+           alt_idx <= alf_data->alf_chroma_num_alt_filters_minus1; alt_idx++) {
+        for (int j = 0; j < 6; j++) {
+          READ_UE_OR_RETURN(&std::get<H266AlfData>(aps->data)
+                                 .alf_chroma_coeff_abs[alt_idx][j]);
+          IN_RANGE_OR_RETURN(alf_data->alf_chroma_coeff_abs[alt_idx][j], 0,
+                             128);
+          if (alf_data->alf_chroma_coeff_abs[alt_idx][j] > 0) {
+            READ_BOOL_OR_RETURN(&std::get<H266AlfData>(aps->data)
+                                     .alf_chroma_coeff_sign[alt_idx][j]);
+          } else {
+            alf_data->alf_chroma_coeff_sign[alt_idx][j] = 0;
+          }
+        }
+
+        if (alf_data->alf_chroma_clip_flag) {
+          for (int j = 0; j < 6; j++) {
+            READ_BITS_OR_RETURN(2, &alf_data->alf_chroma_clip_idx[alt_idx][j]);
+          }
+        }
+      }
+    } else {
+      alf_data->alf_chroma_clip_flag = 0;
+    }
+
+    if (alf_data->alf_cc_cb_filter_signal_flag) {
+      READ_UE_OR_RETURN(&alf_data->alf_cc_cb_filters_signalled_minus1);
+      IN_RANGE_OR_RETURN(alf_data->alf_cc_cb_filters_signalled_minus1, 0, 3);
+
+      for (int k = 0; k < alf_data->alf_cc_cb_filters_signalled_minus1 + 1;
+           k++) {
+        for (int j = 0; j < 7; j++) {
+          READ_BITS_OR_RETURN(3, &alf_data->alf_cc_cb_mapped_coeff_abs[k][j]);
+          if (alf_data->alf_cc_cb_mapped_coeff_abs[k][j]) {
+            READ_BOOL_OR_RETURN(&alf_data->alf_cc_cb_coeff_sign[k][j]);
+          } else {
+            alf_data->alf_cc_cb_coeff_sign[k][j] = 0;
+          }
+        }
+      }
+    }
+
+    if (alf_data->alf_cc_cr_filter_signal_flag) {
+      READ_UE_OR_RETURN(&alf_data->alf_cc_cr_filters_signalled_minus1);
+      IN_RANGE_OR_RETURN(alf_data->alf_cc_cr_filters_signalled_minus1, 0, 3);
+
+      for (int k = 0; k < alf_data->alf_cc_cr_filters_signalled_minus1 + 1;
+           k++) {
+        for (int j = 0; j < 7; j++) {
+          READ_BITS_OR_RETURN(3, &alf_data->alf_cc_cr_mapped_coeff_abs[k][j]);
+          if (alf_data->alf_cc_cr_mapped_coeff_abs[k][j]) {
+            READ_BOOL_OR_RETURN(&alf_data->alf_cc_cr_coeff_sign[k][j]);
+          } else {
+            alf_data->alf_cc_cr_coeff_sign[k][j] = 0;
+          }
+        }
+      }
+    }
+  } else if (aps->aps_params_type == H266APS::kLmcs) {
+    H266LmcsData* lmcs_data = &std::get<H266LmcsData>(aps->data);
+    READ_UE_OR_RETURN(&lmcs_data->lmcs_min_bin_idx);
+    IN_RANGE_OR_RETURN(lmcs_data->lmcs_min_bin_idx, 0, 15);
+    READ_UE_OR_RETURN(&lmcs_data->lmcs_delta_max_bin_idx);
+    IN_RANGE_OR_RETURN(lmcs_data->lmcs_delta_max_bin_idx, 0, 15);
+    READ_UE_OR_RETURN(&lmcs_data->lmcs_delta_cw_prec_minus1);
+    IN_RANGE_OR_RETURN(lmcs_data->lmcs_delta_cw_prec_minus1, 0, 14);
+
+    int lmcs_max_bin_idx = 15 - lmcs_data->lmcs_delta_max_bin_idx;
+    TRUE_OR_RETURN(lmcs_max_bin_idx >= lmcs_data->lmcs_min_bin_idx);
+
+    for (int i = lmcs_data->lmcs_min_bin_idx; i <= lmcs_max_bin_idx; i++) {
+      READ_BITS_OR_RETURN(lmcs_data->lmcs_delta_cw_prec_minus1 + 1,
+                          &lmcs_data->lmcs_delta_abs_cw[i]);
+      if (lmcs_data->lmcs_delta_abs_cw[i] > 0) {
+        READ_BOOL_OR_RETURN(&lmcs_data->lmcs_delta_sign_cw_flag[i]);
+      } else {
+        lmcs_data->lmcs_delta_sign_cw_flag[i] = 0;
+      }
+    }
+
+    if (aps->aps_chroma_present_flag) {
+      READ_BITS_OR_RETURN(3, &lmcs_data->lmcs_delta_abs_crs);
+      if (lmcs_data->lmcs_delta_abs_crs > 0) {
+        READ_BOOL_OR_RETURN(&lmcs_data->lmcs_delta_sign_crs_flag);
+      } else {
+        lmcs_data->lmcs_delta_sign_crs_flag = 0;
+      }
+    } else {
+      lmcs_data->lmcs_delta_abs_crs = 0;
+      lmcs_data->lmcs_delta_sign_crs_flag = 0;
+    }
+  } else if (aps->aps_params_type == H266APS::kScalingList) {
+    // VVC defines default quantization matrices(QM) for INTER_2x2,
+    // INTER_4x4, INTRA_8x8 & INTER_8x8 with flat value of 16 in them.
+    // Other sizes, including 16x16, 32x32, 64x64 are upsampled from the 8x8
+    // quantization matrix.
+    // If explicit scaling list is signaled in SPS/PH/SH/APS, VVC allows encoder
+    // customize up to 28 quantization matrices. For QM of 16x16, 32x32 and
+    // 64x64, the DC values are coded explicitly, while only 64(8x8) AC values
+    // for every such matrix may be coded explicitly, with the entire matrix
+    // upsampled to desired size.
+
+    H266ScalingListData* scaling_list_data =
+        &(std::get<H266ScalingListData>(aps->data));
+
+    int max_id_delta = 0, matrix_size = 0, ref_id = 0;
+    int scaling_matrix_pred2x2[2][2][2], scaling_matrix_pred4x4[6][4][4],
+        scaling_matrix_pred8x8[20][8][8];
+    int scaling_matrix_dc_pred[28];
+
+    // id: [0, 1]:   2x2,   INTRA2x2 & INTER2x2
+    //     [2, 7]:   4x4,   INTRA4x4_Y|U|V & INTER4x4_Y|U|V
+    //     [8, 13]:  8x8,   INTRA8x8_Y|U|V & INTER8x8_Y|U|V
+    //     [14, 19]: 16x16, INTRA16x16_Y|U|V & INTER16x16_Y|U|V
+    //     [20, 25]: 32x32, INTRA32x32_Y|U|V & INTER32x32_Y|U|V
+    //     [26, 27]: 64x64, INTRA64x64_Y & INTER64x64_Y
+
+    for (int id = 0; id < 28; id++) {
+      // Equation 101
+      max_id_delta = (id < 2) ? id : ((id < 8) ? (id - 2) : (id - 8));
+      // Equation 103
+      matrix_size = (id < 2) ? 2 : ((id < 8) ? 4 : 8);
+
+      scaling_list_data->scaling_list_copy_mode_flag[id] = 1;
+      if (aps->aps_chroma_present_flag || id % 3 == 2 || id == 27) {
+        READ_BOOL_OR_RETURN(
+            &scaling_list_data->scaling_list_copy_mode_flag[id]);
+        if (!scaling_list_data->scaling_list_copy_mode_flag[id]) {
+          READ_BOOL_OR_RETURN(
+              &scaling_list_data->scaling_list_pred_mode_flag[id]);
+        }
+
+        // id 0/2/8 are for 2x2/4x4/8x8 initial lists so they don't have
+        // the scaling_list_pred_id_delta syntax signaled.
+        if ((scaling_list_data->scaling_list_copy_mode_flag[id] ||
+             scaling_list_data->scaling_list_pred_mode_flag[id]) &&
+            id != 0 && id != 2 && id != 8) {
+          READ_UE_OR_RETURN(&scaling_list_data->scaling_list_pred_id_delta[id]);
+          IN_RANGE_OR_RETURN(scaling_list_data->scaling_list_pred_id_delta[id],
+                             0, max_id_delta);
+        }
+
+        if (!scaling_list_data->scaling_list_copy_mode_flag[id]) {
+          int next_coef = 0;
+          if (id > 13) {
+            READ_SE_OR_RETURN(
+                &scaling_list_data->scaling_list_dc_coef[id - 14]);
+            IN_RANGE_OR_RETURN(scaling_list_data->scaling_list_dc_coef[id - 14],
+                               -128, 127);
+
+            next_coef += scaling_list_data->scaling_matrix_dc_rec[id - 14];
+          }
+
+          for (int i = 0; i < matrix_size * matrix_size; i++) {
+            int x = kDiagScanOrder8x8[i][0], y = kDiagScanOrder8x8[i][1];
+            if (!(id > 25 && x >= 4 && y >= 4)) {
+              READ_SE_OR_RETURN(
+                  &scaling_list_data->scaling_list_delta_coef[id][i]);
+              IN_RANGE_OR_RETURN(
+                  scaling_list_data->scaling_list_delta_coef[id][i], -128, 127);
+
+              next_coef += scaling_list_data->scaling_list_delta_coef[id][i];
+            }
+            if (id < 2) {
+              scaling_list_data->scaling_list_2x2[id][i] = next_coef;
+            } else if (id < 8) {
+              scaling_list_data->scaling_list_4x4[id - 2][i] = next_coef;
+            } else {
+              scaling_list_data->scaling_list_8x8[id - 8][i] = next_coef;
+            }
+          }
+        }
+
+        // Equation 102
+        ref_id = id - scaling_list_data->scaling_list_pred_id_delta[id];
+
+        if (!scaling_list_data->scaling_list_copy_mode_flag[id] &&
+            !scaling_list_data->scaling_list_pred_mode_flag[id]) {
+          scaling_matrix_dc_pred[id] = 8;
+          if (id < 2) {
+            std::fill_n(scaling_matrix_pred2x2[id][0],
+                        matrix_size * matrix_size, 8);
+          } else if (id < 8) {
+            std::fill_n(scaling_matrix_pred4x4[id - 2][0],
+                        matrix_size * matrix_size, 8);
+          } else {
+            std::fill_n(scaling_matrix_pred8x8[id - 8][0],
+                        matrix_size * matrix_size, 8);
+          }
+        } else if (scaling_list_data->scaling_list_pred_id_delta[id] == 0) {
+          scaling_matrix_dc_pred[id] = 16;
+          if (id < 2) {
+            std::fill_n(scaling_matrix_pred2x2[id][0],
+                        matrix_size * matrix_size, 16);
+          } else if (id < 8) {
+            std::fill_n(scaling_matrix_pred4x4[id - 2][0],
+                        matrix_size * matrix_size, 16);
+          } else {
+            std::fill_n(scaling_matrix_pred8x8[id - 8][0],
+                        matrix_size * matrix_size, 16);
+          }
+        } else {
+          if (id < 2 && id > 0 & ref_id >= 0) {
+            memcpy(&scaling_matrix_pred2x2[id][0],
+                   &scaling_list_data->scaling_matrix_rec_2x2[ref_id][0][0],
+                   4 * sizeof(int));
+          } else if (id < 8 && id > 2 && ref_id >= 2) {
+            memcpy(&scaling_matrix_pred4x4[id - 2][0][0],
+                   &scaling_list_data->scaling_matrix_rec_4x4[ref_id - 2][0][0],
+                   16 * sizeof(int));
+          } else if (ref_id >= 8) {
+            memcpy(&scaling_matrix_pred8x8[id - 8][0][0],
+                   &scaling_list_data->scaling_matrix_rec_8x8[ref_id - 8][0][0],
+                   64 * sizeof(int));
+          }
+          if (ref_id > 13) {
+            scaling_matrix_dc_pred[id] =
+                scaling_list_data->scaling_matrix_dc_rec[ref_id - 14];
+          } else {
+            if (id < 2) {
+              scaling_matrix_dc_pred[id] = scaling_matrix_pred2x2[id][0][0];
+            } else if (id < 8) {
+              scaling_matrix_dc_pred[id] = scaling_matrix_pred4x4[id - 2][0][0];
+            } else {
+              scaling_matrix_dc_pred[id] = scaling_matrix_pred8x8[id - 8][0][0];
+            }
+          }
+        }
+
+        // Equation 104
+        if (id > 13) {
+          scaling_list_data->scaling_matrix_dc_rec[id - 14] =
+              (scaling_matrix_dc_pred[id] +
+               scaling_list_data->scaling_list_dc_coef[id - 14]) &
+              255;
+        }
+      }
+
+      // Equation 105
+      int rec_x = 0, rec_y = 0, k = 0;
+      if (id < 2) {
+        for (k = 0; k <= 3; k++) {
+          rec_x = kDiagScanOrder2x2[k][0];
+          rec_y = kDiagScanOrder2x2[k][1];
+          scaling_list_data->scaling_matrix_rec_2x2[id][rec_x][rec_y] =
+              (scaling_matrix_pred2x2[id][rec_x][rec_y] +
+               scaling_list_data->scaling_list_2x2[id][k]) &
+              255;
+        }
+      } else if (id < 8) {
+        for (k = 0; k <= 15; k++) {
+          rec_x = kDiagScanOrder4x4[k][0];
+          rec_y = kDiagScanOrder4x4[k][1];
+          scaling_list_data->scaling_matrix_rec_4x4[id - 2][rec_x][rec_y] =
+              (scaling_matrix_pred4x4[id - 2][rec_x][rec_y] +
+               scaling_list_data->scaling_list_4x4[id - 2][k]) &
+              255;
+        }
+      } else {
+        for (k = 0; k <= 63; k++) {
+          rec_x = kDiagScanOrder8x8[k][0];
+          rec_y = kDiagScanOrder8x8[k][1];
+          scaling_list_data->scaling_matrix_rec_8x8[id - 8][rec_x][rec_y] =
+              (scaling_matrix_pred8x8[id - 8][rec_x][rec_y] +
+               scaling_list_data->scaling_list_8x8[id - 8][k]) &
+              255;
+        }
+      }
+    }
+  }
+
+  // If an APS with the same id already exists, replace it.
+  *aps_id = aps->aps_adaptation_parameter_set_id;
+  switch (aps->aps_params_type) {
+    case 0:
+      *type = H266APS::ParamType::kAlf;
+      active_alf_aps_[*aps_id] = std::move(aps);
+      break;
+    case 1:
+      *type = H266APS::ParamType::kLmcs;
+      active_lmcs_aps_[*aps_id] = std::move(aps);
+      break;
+    case 2:
+      *type = H266APS::ParamType::kScalingList;
+      active_scaling_list_aps_[*aps_id] = std::move(aps);
+      break;
+  }
+
+  return kOk;
+}
+
 const H266VPS* H266Parser::GetVPS(int vps_id) const {
   auto it = active_vps_.find(vps_id);
   if (it == active_vps_.end()) {
@@ -2472,4 +2916,34 @@
   return it->second.get();
 }
 
+const H266APS* H266Parser::GetAPS(const H266APS::ParamType& type,
+                                  int aps_id) const {
+  switch (type) {
+    case H266APS::ParamType::kAlf: {
+      auto it = active_alf_aps_.find(aps_id);
+      if (it == active_alf_aps_.end()) {
+        DVLOG(1) << "Requested a nonexistent ALF APS id " << aps_id;
+        return nullptr;
+      }
+      return it->second.get();
+    }
+    case H266APS::ParamType::kLmcs: {
+      auto it = active_lmcs_aps_.find(aps_id);
+      if (it == active_lmcs_aps_.end()) {
+        DVLOG(1) << "Requested a nonexistent LMCS APS id " << aps_id;
+        return nullptr;
+      }
+      return it->second.get();
+    }
+    case H266APS::ParamType::kScalingList: {
+      auto it = active_scaling_list_aps_.find(aps_id);
+      if (it == active_scaling_list_aps_.end()) {
+        DVLOG(1) << "Requested a nonexistent ScalingData APS id " << aps_id;
+        return nullptr;
+      }
+      return it->second.get();
+    }
+  }
+}
+
 }  // namespace media
diff --git a/media/video/h266_parser.h b/media/video/h266_parser.h
index ffe590e..52c58dc 100644
--- a/media/video/h266_parser.h
+++ b/media/video/h266_parser.h
@@ -12,6 +12,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <variant>
 #include <vector>
 
 #include "base/containers/flat_map.h"
@@ -51,6 +52,7 @@
   kMaxTiles = 990,       // A.4.1 Table A.1
   kMaxTileRows = 990,    // 990 tile rows and 1 column.
   kMaxTileColumns = 30,  // A.4.1  Table A.1
+  kNumAlfFilters = 25,   // 7.3.18 Adaptive loop filter semantics
 };
 
 // Section 7.4.4.2
@@ -622,6 +624,96 @@
   int num_slices_in_subpic[kMaxSlices];
 };
 
+// 7.3.2.18
+struct MEDIA_EXPORT H266AlfData {
+  H266AlfData();
+
+  // Syntax element
+  bool alf_luma_filter_signal_flag;
+  bool alf_chroma_filter_signal_flag;
+  bool alf_cc_cb_filter_signal_flag;
+  bool alf_cc_cr_filter_signal_flag;
+  bool alf_luma_clip_flag;
+  int alf_luma_num_filters_signalled_minus1;
+  int alf_luma_coeff_delta_idx[25];
+  int alf_luma_coeff_abs[25][12];
+  bool alf_luma_coeff_sign[25][12];
+  int alf_luma_clip_idx[25][12];
+  bool alf_chroma_clip_flag;
+  int alf_chroma_num_alt_filters_minus1;
+  int alf_chroma_coeff_abs[8][6];
+  bool alf_chroma_coeff_sign[8][6];
+  int alf_chroma_clip_idx[8][6];
+  int alf_cc_cb_filters_signalled_minus1;
+  int alf_cc_cb_mapped_coeff_abs[4][7];
+  bool alf_cc_cb_coeff_sign[4][7];
+  int alf_cc_cr_filters_signalled_minus1;
+  int alf_cc_cr_mapped_coeff_abs[4][7];
+  bool alf_cc_cr_coeff_sign[4][7];
+};
+
+// 7.3.2.19
+struct MEDIA_EXPORT H266LmcsData {
+  H266LmcsData();
+
+  // Syntax elements.
+  int lmcs_min_bin_idx;
+  int lmcs_delta_max_bin_idx;
+  int lmcs_delta_cw_prec_minus1;
+  int lmcs_delta_abs_cw[16];
+  bool lmcs_delta_sign_cw_flag[16];
+  int lmcs_delta_abs_crs;
+  bool lmcs_delta_sign_crs_flag;
+};
+
+// 7.3.2.20
+struct MEDIA_EXPORT H266ScalingListData {
+  H266ScalingListData();
+
+  // Syntax elements.
+  bool scaling_list_copy_mode_flag[28];
+  bool scaling_list_pred_mode_flag[28];
+  int scaling_list_pred_id_delta[28];
+  // dc coef for id in [14, 27]
+  int scaling_list_dc_coef[14];
+  int scaling_list_delta_coef[28][64];
+
+  // Calculated values.
+  int scaling_matrix_dc_rec[14];
+  int scaling_list_2x2[2][4];
+  int scaling_list_4x4[6][16];
+  int scaling_list_8x8[20][64];
+  int scaling_matrix_rec_2x2[2][2][2];
+  int scaling_matrix_rec_4x4[6][4][4];
+  int scaling_matrix_rec_8x8[20][8][8];
+};
+
+// 7.3.2.6 Adaptation parameter set
+struct MEDIA_EXPORT H266APS {
+  H266APS(int aps_type);
+  ~H266APS();
+
+  // Table 6.
+  enum ParamType {
+    kAlf = 0,
+    kLmcs = 1,
+    kScalingList = 2,
+  };
+
+  // Use to track if current APS is from PREFIX_APS or SUFFIX_APS,
+  // and the layer id this APS is supposed to apply to.
+  int nal_unit_type;
+  int nuh_layer_id;
+
+  // Syntax elements
+  int aps_params_type;
+  int aps_adaptation_parameter_set_id;
+  bool aps_chroma_present_flag;
+  bool aps_extension_flag;
+
+  std::variant<H266AlfData, H266LmcsData, H266ScalingListData> data;
+};
+
 // Class to parse an Annex-B H.266 stream.
 class MEDIA_EXPORT H266Parser : public H266NaluParser {
  public:
@@ -654,18 +746,26 @@
   // the returned |*pps_id| as parameter.
   Result ParsePPS(const H266NALU& nalu, int* pps_id);
 
-  // Return a pointer to VPS with given |*vps_id| or
+  // Parse an APS NALU and save its data in the parser, returning id
+  // of the parsed structure in |*aps_id| and APS type in |*type|. To get a
+  // pointer to a given APS structure, use GetAPS(), passing the returned
+  // |*aps_id| and |*type| as parameter.
+  Result ParseAPS(const H266NALU& nalu, int* pps_id, H266APS::ParamType* type);
+
+  // Return a pointer to VPS with given |vps_id| or
   // null if not present.
   const H266VPS* GetVPS(int vps_id) const;
 
-  // Return a pointer to SPS with given |*sps_id| or
-  // null if not present.
+  // Return a pointer to SPS with given |sps_id| or null if not present.
   const H266SPS* GetSPS(int sps_id) const;
 
-  // Return a pointer to PPS with given |*pps_id| or
-  // null if not present.
+  // Return a pointer to PPS with given |pps_id| or null if not present.
   const H266PPS* GetPPS(int pps_id) const;
 
+  // Return a pointer to APS with given |aps_id| and |type| or null if not
+  // present.
+  const H266APS* GetAPS(const H266APS::ParamType& type, int aps_id) const;
+
   static VideoCodecProfile ProfileIDCToVideoCodecProfile(int profile_idc);
 
  private:
@@ -704,6 +804,11 @@
   base::flat_map<int, std::unique_ptr<H266VPS>> active_vps_;
   base::flat_map<int, std::unique_ptr<H266SPS>> active_sps_;
   base::flat_map<int, std::unique_ptr<H266PPS>> active_pps_;
+  // APS of same aps_params_type share the same value space of APS ID regardless
+  // of their NALU type or nuh_layer_id.
+  base::flat_map<int, std::unique_ptr<H266APS>> active_alf_aps_;
+  base::flat_map<int, std::unique_ptr<H266APS>> active_lmcs_aps_;
+  base::flat_map<int, std::unique_ptr<H266APS>> active_scaling_list_aps_;
 };
 
 }  // namespace media
diff --git a/media/video/h266_parser_fuzzertest.cc b/media/video/h266_parser_fuzzertest.cc
index 038ebc6c..b1e4df9 100644
--- a/media/video/h266_parser_fuzzertest.cc
+++ b/media/video/h266_parser_fuzzertest.cc
@@ -37,6 +37,13 @@
       case media::H266NALU::kPPS:
         int pps_id;
         res = parser.ParsePPS(nalu, &pps_id);
+        break;
+      case media::H266NALU::kPrefixAPS:
+      case media::H266NALU::kSuffixAPS:
+        media::H266APS::ParamType aps_type;
+        int aps_id;
+        res = parser.ParseAPS(nalu, &aps_id, &aps_type);
+        break;
       // TODO(crbugs.com/1417910): Other NALU types will be checked.
       default:
         // Skip other NALU types
diff --git a/media/video/h266_parser_unittest.cc b/media/video/h266_parser_unittest.cc
index 290fbcd..f51139b 100644
--- a/media/video/h266_parser_unittest.cc
+++ b/media/video/h266_parser_unittest.cc
@@ -87,6 +87,13 @@
           res = parser_.ParsePPS(nalu, &pps_id);
           EXPECT_TRUE(!!parser_.GetPPS(pps_id));
           break;
+        case H266NALU::kPrefixAPS:
+        case H266NALU::kSuffixAPS:
+          H266APS::ParamType aps_type;
+          int aps_id;
+          res = parser_.ParseAPS(nalu, &aps_id, &aps_type);
+          EXPECT_TRUE(!!parser_.GetAPS(aps_type, aps_id));
+          break;
         // TODO(crbugs.com/1417910): add more NALU types.
         default:
           break;
@@ -749,4 +756,438 @@
   EXPECT_EQ(pps->pps_joint_cbcr_qp_offset_list[3], 0);
 }
 
+// Verify scaling list parsing in APS.
+TEST_F(H266ParserTest, ParseAPSShouldConstructCorrectScalingLists) {
+  LoadParserFile("bbb_scaling_lists.vvc");
+  H266NALU target_nalu;
+  int sps_id;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kSPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseSPS(target_nalu, &sps_id));
+  // Parsing of the SPS should generate fake VPS with vps_id = 0;
+  const H266VPS* vps = parser_.GetVPS(0);
+  EXPECT_TRUE(!!vps);
+  const H266SPS* sps = parser_.GetSPS(sps_id);
+  EXPECT_TRUE(!!sps);
+  int pps_id;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParsePPS(target_nalu, &pps_id));
+  const H266PPS* pps = parser_.GetPPS(pps_id);
+  EXPECT_TRUE(!!pps);
+  int aps_id;
+  H266APS::ParamType aps_type;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  const H266APS* aps = parser_.GetAPS(aps_type, aps_id);
+  EXPECT_TRUE(!!aps);
+
+  EXPECT_EQ(aps->aps_params_type, 2);
+  EXPECT_EQ(aps->aps_adaptation_parameter_set_id, 0);
+  EXPECT_TRUE(aps->aps_chroma_present_flag);
+
+  const H266ScalingListData* scaling_list_data =
+      &(std::get<H266ScalingListData>(aps->data));
+  EXPECT_TRUE(!!scaling_list_data);
+
+  // Verify the reconstructed quantization matrices
+  // INTER2x2_CHRAMAU/INTER2x2_CHROMAV
+  int inter2x2_scaling_list_expected[2][2] = {{11, 30}, {30, 50}};
+
+  for (int i = 0; i < 2; i++) {
+    for (int m = 0; m < 2; m++) {
+      for (int n = 0; n < 2; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_2x2[i][m][n],
+                  inter2x2_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTRA4x4_LUMA/INTRA4x4_CHROMAU/INTRA4x4_CHOMRAV
+  int intra4x4_scaling_list_expected[4][4] = {
+      {7, 12, 19, 26}, {12, 16, 24, 40}, {19, 24, 41, 50}, {26, 40, 50, 56}};
+
+  for (int i = 0; i <= 2; i++) {
+    for (int m = 0; m < 4; m++) {
+      for (int n = 0; n < 4; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_4x4[i][m][n],
+                  intra4x4_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTER4x4_LUMA/INTER4x4_CHROMAU/INTER4x4_CHROMAV
+  int inter4x4_scaling_list_expected[4][4] = {
+      {11, 18, 30, 43}, {18, 22, 40, 50}, {30, 40, 50, 52}, {43, 50, 52, 55}};
+
+  for (int i = 3; i < 6; i++) {
+    for (int m = 0; m < 4; m++) {
+      for (int n = 0; n < 4; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_4x4[i][m][n],
+                  inter4x4_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTRA8x8_LUMA/INTRA8x8_CHROMAU/INTRA8x8_CHROMAV
+  int intra8x8_scaling_list_expected[8][8] = {
+      {6, 9, 13, 18, 25, 35, 36, 37},   {9, 10, 15, 21, 32, 35, 37, 41},
+      {13, 15, 18, 23, 35, 55, 58, 59}, {18, 21, 23, 26, 65, 58, 64, 66},
+      {25, 32, 35, 65, 66, 66, 67, 70}, {35, 35, 55, 58, 66, 68, 70, 73},
+      {36, 37, 58, 64, 67, 70, 76, 80}, {37, 41, 59, 66, 70, 73, 80, 85}};
+
+  for (int i = 0; i <= 2; i++) {
+    for (int m = 0; m < 8; m++) {
+      for (int n = 0; n < 8; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[i][m][n],
+                  intra8x8_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTER8x8_LUMA/INTER8x8_CHROMAU/INTER8x8_CHROMAV
+  int inter8x8_scaling_list_expected[8][8] = {
+      {9, 15, 20, 29, 36, 38, 42, 43},  {15, 17, 22, 29, 39, 43, 45, 46},
+      {20, 22, 32, 34, 47, 48, 49, 50}, {29, 29, 34, 44, 50, 51, 52, 53},
+      {36, 39, 47, 50, 51, 52, 55, 55}, {38, 43, 48, 51, 52, 53, 56, 58},
+      {42, 45, 49, 52, 55, 56, 55, 60}, {43, 46, 50, 53, 55, 58, 60, 63}};
+
+  for (int i = 3; i < 6; i++) {
+    for (int m = 0; m < 8; m++) {
+      for (int n = 0; n < 8; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[i][m][n],
+                  inter8x8_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTRA16x16_LUMA
+  for (int m = 0; m < 8; m++) {
+    for (int n = 0; n < 8; n++) {
+      EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[6][m][n],
+                intra8x8_scaling_list_expected[m][n]);
+    }
+  }
+
+  // INTRA16x16_CHROMAU/INTRA16x16_CHROMAV
+  int intra16x16_scaling_list_expected[8][8] = {
+      {7, 9, 13, 18, 25, 35, 36, 37},   {9, 10, 15, 21, 32, 35, 37, 41},
+      {13, 15, 18, 23, 35, 55, 58, 59}, {18, 21, 23, 26, 65, 58, 64, 66},
+      {25, 32, 35, 65, 66, 66, 67, 70}, {35, 35, 55, 58, 66, 68, 70, 73},
+      {36, 37, 58, 64, 67, 70, 76, 80}, {37, 41, 59, 66, 70, 73, 80, 85}};
+
+  for (int i = 7; i < 9; i++) {
+    for (int m = 0; m < 8; m++) {
+      for (int n = 0; n < 8; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[i][m][n],
+                  intra16x16_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTER16x16_LUMA/INTER16x16_CHROMAU/INTER16x16_CHROMAV
+  int inter16x16_scaling_list_expected[8][8] = {
+      {11, 15, 20, 29, 36, 38, 42, 43}, {15, 17, 22, 29, 39, 43, 45, 46},
+      {20, 22, 32, 34, 47, 48, 49, 50}, {29, 29, 34, 44, 50, 51, 52, 53},
+      {36, 39, 47, 50, 51, 52, 55, 55}, {38, 43, 48, 51, 52, 53, 56, 58},
+      {42, 45, 49, 52, 55, 56, 55, 60}, {43, 46, 50, 53, 55, 58, 60, 63}};
+
+  for (int i = 9; i < 12; i++) {
+    for (int m = 0; m < 8; m++) {
+      for (int n = 0; n < 8; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[i][m][n],
+                  inter16x16_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTRA32x32_LUMA/INTRA32x32_CHROMAU/INTRA32x32_CHROMAV
+  // The test clip reuses 8x8 list.
+  for (int i = 12; i < 15; i++) {
+    for (int m = 0; m < 8; m++) {
+      for (int n = 0; n < 8; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[i][m][n],
+                  intra16x16_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTER32x32_LUMA/INTER32x32_CHROMAU/INTER32x32_CHROMAV
+  // The test clip reuses 8x8 list.
+  for (int i = 15; i < 18; i++) {
+    for (int m = 0; m < 8; m++) {
+      for (int n = 0; n < 8; n++) {
+        EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[i][m][n],
+                  inter16x16_scaling_list_expected[m][n]);
+      }
+    }
+  }
+
+  // INTRA64x64_LUMA
+  int intra64x64_scaling_list_expected[8][8] = {
+      {8, 9, 13, 18, 25, 35, 36, 37},   {9, 12, 15, 20, 32, 35, 37, 41},
+      {13, 15, 18, 23, 35, 55, 58, 59}, {18, 21, 23, 26, 65, 58, 64, 66},
+      {25, 32, 35, 65, 66, 66, 67, 70}, {35, 35, 55, 58, 66, 68, 70, 73},
+      {36, 37, 58, 64, 67, 70, 76, 80}, {37, 41, 59, 66, 70, 73, 80, 85}};
+
+  for (int m = 0; m < 8; m++) {
+    for (int n = 0; n < 8; n++) {
+      EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[18][m][n],
+                intra64x64_scaling_list_expected[m][n]);
+    }
+  }
+
+  // INTER64x64_LUMA
+  int inter64x64_scaling_list_expected[8][8] = {
+      {11, 15, 20, 29, 36, 38, 42, 43}, {14, 17, 23, 29, 38, 43, 45, 46},
+      {20, 22, 32, 34, 47, 48, 49, 50}, {29, 29, 34, 44, 50, 51, 52, 53},
+      {36, 39, 47, 50, 51, 52, 55, 55}, {38, 43, 48, 51, 52, 53, 56, 58},
+      {42, 45, 49, 52, 55, 56, 55, 60}, {43, 46, 50, 53, 55, 58, 60, 63}};
+
+  for (int m = 0; m < 8; m++) {
+    for (int n = 0; n < 8; n++) {
+      EXPECT_EQ(scaling_list_data->scaling_matrix_rec_8x8[19][m][n],
+                inter64x64_scaling_list_expected[m][n]);
+    }
+  }
+
+  // Verify the reconstructed DC array
+  int quantization_matrix_dc_expected[14] = {6, 6, 6, 9, 9, 9, 6,
+                                             6, 6, 9, 9, 9, 6, 9};
+
+  for (int i = 0; i < 14; i++) {
+    EXPECT_EQ(scaling_list_data->scaling_matrix_dc_rec[i],
+              quantization_matrix_dc_expected[i]);
+  }
+}
+
+// Verify adaptive loop filter syntax parsing in APS.
+TEST_F(H266ParserTest, ParseAPSShouldConstructCorrectAlfData) {
+  LoadParserFile("bear_180p.vvc");
+  H266NALU target_nalu;
+  int sps_id;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kSPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseSPS(target_nalu, &sps_id));
+  // Parsing of the SPS should generate fake VPS with vps_id = 0;
+  const H266VPS* vps = parser_.GetVPS(0);
+  EXPECT_TRUE(!!vps);
+  const H266SPS* sps = parser_.GetSPS(sps_id);
+  EXPECT_TRUE(!!sps);
+  int pps_id;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParsePPS(target_nalu, &pps_id));
+  const H266PPS* pps = parser_.GetPPS(pps_id);
+  EXPECT_TRUE(!!pps);
+  int aps_id;
+  H266APS::ParamType aps_type;
+
+  // Parse the first ALF APS with id = 7.
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  const H266APS* aps = parser_.GetAPS(aps_type, aps_id);
+  EXPECT_TRUE(!!aps);
+
+  const H266AlfData* alf = &(std::get<H266AlfData>(aps->data));
+  EXPECT_TRUE(!!alf);
+
+  EXPECT_EQ(aps->aps_params_type, 0);
+  EXPECT_EQ(aps->aps_adaptation_parameter_set_id, 7);
+  EXPECT_TRUE(aps->aps_chroma_present_flag);
+  EXPECT_TRUE(alf->alf_luma_filter_signal_flag);
+  EXPECT_FALSE(alf->alf_cc_cb_filter_signal_flag);
+  EXPECT_FALSE(alf->alf_cc_cr_filter_signal_flag);
+  EXPECT_FALSE(alf->alf_luma_clip_flag);
+  EXPECT_EQ(alf->alf_luma_num_filters_signalled_minus1, 9);
+
+  // Verify the luma coeff delta index.
+  int luma_coeff_delta_idx_expected[25] = {0, 1, 2, 3, 4, 0, 0, 2, 5,
+                                           6, 0, 0, 0, 0, 0, 0, 1, 7,
+                                           4, 5, 0, 6, 8, 2, 9};
+
+  for (int i = 0; i < 25; i++) {
+    EXPECT_EQ(alf->alf_luma_coeff_delta_idx[i],
+              luma_coeff_delta_idx_expected[i]);
+  }
+
+  // Verify the luma coeff absolute values. Verify only the first group.
+  int luma_coeff_abs_expected[12] = {1, 5, 1, 1, 4, 2, 18, 7, 8, 3, 7, 21};
+
+  for (int i = 0; i < 12; i++) {
+    EXPECT_EQ(alf->alf_luma_coeff_abs[0][i], luma_coeff_abs_expected[i]);
+  }
+
+  EXPECT_FALSE(alf->alf_chroma_clip_flag);
+  EXPECT_EQ(alf->alf_chroma_num_alt_filters_minus1, 3);
+
+  // Verify the chroma coeff absolute values. Verify only the last group.
+  int chroma_coeff_abs_expected[6] = {0, 3, 4, 3, 7, 11};
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(alf->alf_chroma_coeff_abs[3][i], chroma_coeff_abs_expected[i]);
+  }
+
+  // Parse the second ALF APS with same id = 7. This should override previously
+  // parsed APS with same id.
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  const H266APS* aps_updated = parser_.GetAPS(aps_type, aps_id);
+  EXPECT_TRUE(!!aps_updated);
+
+  const H266AlfData* alf_updated = &(std::get<H266AlfData>(aps_updated->data));
+  EXPECT_TRUE(!!alf_updated);
+
+  EXPECT_EQ(aps_updated->aps_params_type, 0);
+  EXPECT_EQ(aps_updated->aps_adaptation_parameter_set_id, 7);
+  EXPECT_TRUE(aps_updated->aps_chroma_present_flag);
+  EXPECT_TRUE(alf_updated->alf_luma_filter_signal_flag);
+  EXPECT_FALSE(alf_updated->alf_cc_cb_filter_signal_flag);
+  EXPECT_FALSE(alf_updated->alf_cc_cr_filter_signal_flag);
+  EXPECT_FALSE(alf_updated->alf_luma_clip_flag);
+  EXPECT_EQ(alf_updated->alf_luma_num_filters_signalled_minus1, 9);
+
+  // Verify the luma coeff delta index.
+  int luma_coeff_delta_idx_expected_updated[25] = {0, 1, 2, 3, 3, 4, 1, 5, 3,
+                                                   6, 0, 0, 0, 0, 0, 0, 4, 5,
+                                                   7, 7, 1, 1, 8, 9, 3};
+
+  for (int i = 0; i < 25; i++) {
+    EXPECT_EQ(alf_updated->alf_luma_coeff_delta_idx[i],
+              luma_coeff_delta_idx_expected_updated[i]);
+  }
+
+  // Verify the luma coeff absolute values. Verify only the first group.
+  int luma_coeff_abs_expected_updated[12] = {1,  5, 7, 8, 8, 8,
+                                             21, 7, 6, 2, 7, 25};
+
+  for (int i = 0; i < 12; i++) {
+    EXPECT_EQ(alf_updated->alf_luma_coeff_abs[0][i],
+              luma_coeff_abs_expected_updated[i]);
+  }
+
+  EXPECT_FALSE(alf_updated->alf_chroma_clip_flag);
+  EXPECT_EQ(alf_updated->alf_chroma_num_alt_filters_minus1, 1);
+
+  // Verify the chroma coeff absolute values. Verify only the last group.
+  int chroma_coeff_abs_expected_updated[6] = {10, 5, 27, 0, 8, 33};
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(alf_updated->alf_chroma_coeff_abs[1][i],
+              chroma_coeff_abs_expected_updated[i]);
+  }
+
+  // Parse the next ALF APS with id = 6.
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  const H266APS* aps3 = parser_.GetAPS(aps_type, aps_id);
+  EXPECT_TRUE(!!aps3);
+
+  const H266AlfData* alf3 = &(std::get<H266AlfData>(aps3->data));
+  EXPECT_TRUE(!!alf3);
+
+  EXPECT_EQ(aps3->aps_params_type, 0);
+  EXPECT_EQ(aps3->aps_adaptation_parameter_set_id, 6);
+  EXPECT_TRUE(aps3->aps_chroma_present_flag);
+  EXPECT_TRUE(alf3->alf_luma_filter_signal_flag);
+  EXPECT_FALSE(alf3->alf_chroma_filter_signal_flag);
+  EXPECT_FALSE(alf3->alf_cc_cb_filter_signal_flag);
+  EXPECT_FALSE(alf3->alf_cc_cr_filter_signal_flag);
+  EXPECT_FALSE(alf3->alf_luma_clip_flag);
+  EXPECT_EQ(alf3->alf_luma_num_filters_signalled_minus1, 3);
+
+  // Current ALF APS contains only luma coeff delta index and absolute values.
+  int luma_coeff_delta_idx_expected3[25] = {0, 0, 1, 2, 2, 3, 0, 3, 2,
+                                            2, 0, 0, 0, 0, 0, 0, 0, 3,
+                                            2, 2, 2, 3, 3, 3, 3};
+  for (int i = 0; i < 25; i++) {
+    EXPECT_EQ(alf3->alf_luma_coeff_delta_idx[i],
+              luma_coeff_delta_idx_expected3[i]);
+  }
+
+  // Parse the entire bitstream till no APS NUT is found, and check number
+  // of APSes stored by parser.
+  while (ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS)) {
+    EXPECT_EQ(H266Parser::kOk,
+              parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  }
+  int stored_ids_of_apses[5] = {3, 4, 5, 6, 7};
+  for (int i = 0; i < 5; i++) {
+    const H266APS* current_aps =
+        parser_.GetAPS(aps_type, stored_ids_of_apses[i]);
+    EXPECT_TRUE(!!current_aps);
+  }
+}
+
+// Verify luma mapping & chroma scaling data syntax parsing in APS.
+TEST_F(H266ParserTest, ParseAPSShouldConstructCorrectLmcsData) {
+  LoadParserFile("basketball_2_layers.vvc");
+  H266NALU target_nalu;
+  int vps_id;
+
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kVPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseVPS(&vps_id));
+  const H266VPS* vps = parser_.GetVPS(vps_id);
+  EXPECT_TRUE(!!vps);
+
+  int sps_id;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kSPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseSPS(target_nalu, &sps_id));
+  const H266SPS* sps = parser_.GetSPS(sps_id);
+  EXPECT_TRUE(!!sps);
+
+  int pps_id;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParsePPS(target_nalu, &pps_id));
+  const H266PPS* pps = parser_.GetPPS(pps_id);
+  EXPECT_TRUE(!!pps);
+
+  int aps_id;
+  H266APS::ParamType aps_type;
+
+  // Parse the first LMCS APS with id = 0.
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS));
+  EXPECT_EQ(H266Parser::kOk, parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  const H266APS* aps1 = parser_.GetAPS(aps_type, aps_id);
+  EXPECT_TRUE(!!aps1);
+
+  const H266LmcsData* lmcs1 = &(std::get<H266LmcsData>(aps1->data));
+  EXPECT_TRUE(!!lmcs1);
+
+  EXPECT_EQ(aps1->aps_params_type, 1);
+  EXPECT_EQ(aps1->aps_adaptation_parameter_set_id, 0);
+  EXPECT_TRUE(aps1->aps_chroma_present_flag);
+  EXPECT_EQ(lmcs1->lmcs_min_bin_idx, 0);
+  EXPECT_EQ(lmcs1->lmcs_delta_max_bin_idx, 1);
+  EXPECT_EQ(lmcs1->lmcs_delta_cw_prec_minus1, 1);
+
+  // LmcsMaxBinIdx of the test stream is 14.
+  int lmcs_delta_abs_cw_expected1[15] = {2, 0, 0, 0, 0, 0, 1, 2,
+                                         1, 0, 0, 0, 0, 0, 2};
+  for (int i = 0; i < 15; i++) {
+    EXPECT_EQ(lmcs1->lmcs_delta_abs_cw[i], lmcs_delta_abs_cw_expected1[i]);
+  }
+  EXPECT_EQ(lmcs1->lmcs_delta_abs_crs, 1);
+  EXPECT_FALSE(lmcs1->lmcs_delta_sign_crs_flag);
+
+  // Parse till the end of the stream and check all stored LMCS APSes
+  while (ParseNalusUntilNut(&target_nalu, H266NALU::kPrefixAPS)) {
+    EXPECT_EQ(H266Parser::kOk,
+              parser_.ParseAPS(target_nalu, &aps_id, &aps_type));
+  }
+
+  int stored_ids_of_apses[4] = {0, 1, 7, 6};
+  for (int i = 0; i < 2; i++) {
+    aps_type = H266APS::ParamType::kLmcs;
+    const H266APS* current_aps =
+        parser_.GetAPS(aps_type, stored_ids_of_apses[i]);
+    EXPECT_TRUE(!!current_aps);
+  }
+
+  for (int i = 2; i < 4; i++) {
+    aps_type = H266APS::ParamType::kAlf;
+    const H266APS* current_aps =
+        parser_.GetAPS(aps_type, stored_ids_of_apses[i]);
+    EXPECT_TRUE(!!current_aps);
+  }
+
+  aps_type = H266APS::ParamType::kLmcs;
+  const H266APS* nonexisting_aps = parser_.GetAPS(aps_type, 2);
+  EXPECT_TRUE(!nonexisting_aps);
+}
+
 }  // namespace media
diff --git a/net/base/platform_mime_util_mac.mm b/net/base/platform_mime_util_mac.mm
index 4c339d9..76ae560 100644
--- a/net/base/platform_mime_util_mac.mm
+++ b/net/base/platform_mime_util_mac.mm
@@ -9,7 +9,7 @@
 
 #include <string>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/notreached.h"
@@ -186,7 +186,7 @@
             continue;
           }
           extensions_found = true;
-          for (NSString* extension in base::mac::CFToNSPtrCast(
+          for (NSString* extension in base::apple::CFToNSPtrCast(
                    extensions_list)) {
             extensions->insert(base::SysNSStringToUTF8(extension));
           }
diff --git a/net/cert/pki/name_constraints.cc b/net/cert/pki/name_constraints.cc
index eed0741..5ab9710 100644
--- a/net/cert/pki/name_constraints.cc
+++ b/net/cert/pki/name_constraints.cc
@@ -11,6 +11,7 @@
 #include "base/numerics/clamped_math.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/common_cert_errors.h"
+#include "net/cert/pki/general_names.h"
 #include "net/cert/pki/string_util.h"
 #include "net/cert/pki/verify_name_match.h"
 #include "net/der/input.h"
@@ -30,9 +31,9 @@
 // that name form appears in the subject field or subjectAltName
 // extension of a subsequent certificate, then the application MUST
 // either process the constraint or reject the certificate.)
-const int kSupportedNameTypes = GENERAL_NAME_DNS_NAME |
-                                GENERAL_NAME_DIRECTORY_NAME |
-                                GENERAL_NAME_IP_ADDRESS;
+const int kSupportedNameTypes =
+    GENERAL_NAME_RFC822_NAME | GENERAL_NAME_DNS_NAME |
+    GENERAL_NAME_DIRECTORY_NAME | GENERAL_NAME_IP_ADDRESS;
 
 // Controls wildcard handling of DNSNameMatches.
 // If WildcardMatchType is WILDCARD_PARTIAL_MATCH "*.bar.com" is considered to
@@ -162,6 +163,107 @@
   return true;
 }
 
+bool IsAlphaDigit(char c) {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
+         (c >= 'A' && c <= 'Z');
+}
+
+// Returns true if 'local_part' contains only characters that are valid in a
+// non-quoted mailbox local-part. Does not check any other part of the syntax
+// requirements. Does not allow whitespace.
+bool IsAllowedRfc822LocalPart(std::string_view local_part) {
+  if (local_part.empty()) {
+    return false;
+  }
+  for (char c : local_part) {
+    if (!(IsAlphaDigit(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
+          c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
+          c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
+          c == '`' || c == '{' || c == '|' || c == '}' || c == '~' ||
+          c == '.')) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Returns true if 'domain' contains only characters that are valid in a
+// mailbox domain. Does not check any other part of the syntax
+// requirements. Does not allow IPv6-address-literal as text IPv6 addresses are
+// non-unique. Does not allow other address literals either as how to handle
+// them with domain/subdomain matching isn't specified/possible.
+bool IsAllowedRfc822Domain(std::string_view domain) {
+  if (domain.empty()) {
+    return false;
+  }
+  for (char c : domain) {
+    if (!(IsAlphaDigit(c) || c == '-' || c == '.')) {
+      return false;
+    }
+  }
+  return true;
+}
+
+enum class Rfc822NameMatchType { kPermitted, kExcluded };
+bool Rfc822NameMatches(std::string_view local_part,
+                       std::string_view domain,
+                       std::string_view rfc822_constraint,
+                       Rfc822NameMatchType match_type,
+                       bool case_insensitive_local_part) {
+  // In case of parsing errors, return a value that will cause the name to not
+  // be permitted.
+  const bool error_value =
+      match_type == Rfc822NameMatchType::kPermitted ? false : true;
+
+  std::vector<std::string_view> constraint_components =
+      net::string_util::SplitString(rfc822_constraint, '@');
+  std::string_view constraint_local_part;
+  std::string_view constraint_domain;
+  if (constraint_components.size() == 1) {
+    constraint_domain = constraint_components[0];
+  } else if (constraint_components.size() == 2) {
+    constraint_local_part = constraint_components[0];
+    if (!IsAllowedRfc822LocalPart(constraint_local_part)) {
+      return error_value;
+    }
+    constraint_domain = constraint_components[1];
+  } else {
+    // If we did the full parsing then it is possible for a @ to be in a quoted
+    // local-part of the name, but we don't do that, so just error if @ appears
+    // more than once.
+    return error_value;
+  }
+  if (!IsAllowedRfc822Domain(constraint_domain)) {
+    return error_value;
+  }
+
+  // RFC 5280 section 4.2.1.10:
+  // To indicate a particular mailbox, the constraint is the complete mail
+  // address.  For example, "root@example.com" indicates the root mailbox on
+  // the host "example.com".
+  if (!constraint_local_part.empty()) {
+    return (case_insensitive_local_part
+                ? string_util::IsEqualNoCase(local_part, constraint_local_part)
+                : local_part == constraint_local_part) &&
+           string_util::IsEqualNoCase(domain, constraint_domain);
+  }
+
+  // RFC 5280 section 4.2.1.10:
+  // To specify any address within a domain, the constraint is specified with a
+  // leading period (as with URIs).  For example, ".example.com" indicates all
+  // the Internet mail addresses in the domain "example.com", but not Internet
+  // mail addresses on the host "example.com".
+  if (constraint_domain.starts_with('.')) {
+    return string_util::EndsWithNoCase(domain, constraint_domain);
+  }
+
+  // RFC 5280 section 4.2.1.10:
+  // To indicate all Internet mail addresses on a particular host, the
+  // constraint is specified as the host name.  For example, the constraint
+  // "example.com" is satisfied by any mail address at the host "example.com".
+  return string_util::IsEqualNoCase(domain, constraint_domain);
+}
+
 }  // namespace
 
 NameConstraints::~NameConstraints() = default;
@@ -247,6 +349,10 @@
 
   if (subject_alt_names) {
     check_count +=
+        base::ClampMul(subject_alt_names->rfc822_names.size(),
+                       base::ClampAdd(excluded_subtrees_.rfc822_names.size(),
+                                      permitted_subtrees_.rfc822_names.size()));
+    check_count +=
         base::ClampMul(subject_alt_names->dns_names.size(),
                        base::ClampAdd(excluded_subtrees_.dns_names.size(),
                                       permitted_subtrees_.dns_names.size()));
@@ -265,6 +371,21 @@
                                   permitted_subtrees_.directory_names.size());
   }
 
+  std::vector<std::string> subject_email_addresses_to_check;
+  if (!subject_alt_names &&
+      (constrained_name_types() & GENERAL_NAME_RFC822_NAME)) {
+    if (!FindEmailAddressesInName(subject_rdn_sequence,
+                                  &subject_email_addresses_to_check)) {
+      // Error parsing |subject_rdn_sequence|.
+      errors->AddError(cert_errors::kNotPermittedByNameConstraints);
+      return;
+    }
+    check_count +=
+        base::ClampMul(subject_email_addresses_to_check.size(),
+                       base::ClampAdd(excluded_subtrees_.rfc822_names.size(),
+                                      permitted_subtrees_.rfc822_names.size()));
+  }
+
   if (check_count > kMaxChecks) {
     errors->AddError(cert_errors::kTooManyNameConstraintChecks);
     return;
@@ -298,6 +419,20 @@
     }
 
     // Check supported name types:
+
+    // Only check rfc822 SANs if any rfc822 constraints are present, since we
+    // might fail if there are email addresses we don't know how to parse but
+    // are technically correct.
+    if (constrained_name_types() & GENERAL_NAME_RFC822_NAME) {
+      for (const auto& rfc822_name : subject_alt_names->rfc822_names) {
+        if (!IsPermittedRfc822Name(
+                rfc822_name, /*case_insensitive_exclude_localpart=*/false)) {
+          errors->AddError(cert_errors::kNotPermittedByNameConstraints);
+          return;
+        }
+      }
+    }
+
     for (const auto& dns_name : subject_alt_names->dns_names) {
       if (!IsPermittedDNSName(dns_name)) {
         errors->AddError(cert_errors::kNotPermittedByNameConstraints);
@@ -329,15 +464,20 @@
   // form, but the certificate does not include a subject alternative name, the
   // rfc822Name constraint MUST be applied to the attribute of type emailAddress
   // in the subject distinguished name.
-  if (!subject_alt_names &&
-      (constrained_name_types() & GENERAL_NAME_RFC822_NAME)) {
-    bool contained_email_address = false;
-    if (!NameContainsEmailAddress(subject_rdn_sequence,
-                                  &contained_email_address)) {
-      errors->AddError(cert_errors::kNotPermittedByNameConstraints);
-      return;
-    }
-    if (contained_email_address) {
+  for (const auto& rfc822_name : subject_email_addresses_to_check) {
+    // Whether local_part should be matched case-sensitive or not is somewhat
+    // unclear. RFC 2821 says that it should be case-sensitive. RFC 2985 says
+    // that emailAddress attributes in a Name are fully case-insensitive.
+    // Some other verifier implementations always do local-part comparison
+    // case-sensitive, while some always do it case-insensitive. Many but not
+    // all SMTP servers interpret addresses as case-insensitive.
+    //
+    // Give how poorly specified this is, and the conflicting implementations
+    // in the wild, this implementation will do case-insensitive match for
+    // excluded names from the subject to avoid potentially allowing
+    // something that wasn't expected.
+    if (!IsPermittedRfc822Name(rfc822_name,
+                               /*case_insensitive_exclude_localpart=*/true)) {
       errors->AddError(cert_errors::kNotPermittedByNameConstraints);
       return;
     }
@@ -360,6 +500,113 @@
   }
 }
 
+bool NameConstraints::IsPermittedRfc822Name(
+    std::string_view name,
+    bool case_insensitive_exclude_localpart) const {
+  // RFC 5280 4.2.1.6.  Subject Alternative Name
+  //
+  // When the subjectAltName extension contains an Internet mail address,
+  // the address MUST be stored in the rfc822Name.  The format of an
+  // rfc822Name is a "Mailbox" as defined in Section 4.1.2 of [RFC2821].
+  // A Mailbox has the form "Local-part@Domain".  Note that a Mailbox has
+  // no phrase (such as a common name) before it, has no comment (text
+  // surrounded in parentheses) after it, and is not surrounded by "<" and
+  // ">".  Rules for encoding Internet mail addresses that include
+  // internationalized domain names are specified in Section 7.5.
+
+  // Relevant parts from RFC 2821 & RFC 2822
+  //
+  // Mailbox = Local-part "@" Domain
+  // Local-part = Dot-string / Quoted-string
+  //       ; MAY be case-sensitive
+  //
+  // Dot-string = Atom *("." Atom)
+  // Atom = 1*atext
+  // Quoted-string = DQUOTE *qcontent DQUOTE
+  //
+  //
+  // atext           =       ALPHA / DIGIT / ; Any character except controls,
+  //                         "!" / "#" /     ;  SP, and specials.
+  //                         "$" / "%" /     ;  Used for atoms
+  //                         "&" / "'" /
+  //                         "*" / "+" /
+  //                         "-" / "/" /
+  //                         "=" / "?" /
+  //                         "^" / "_" /
+  //                         "`" / "{" /
+  //                         "|" / "}" /
+  //                         "~"
+  //
+  // atom            =       [CFWS] 1*atext [CFWS]
+  //
+  //
+  // qtext           =       NO-WS-CTL /     ; Non white space controls
+  //                         %d33 /          ; The rest of the US-ASCII
+  //                         %d35-91 /       ;  characters not including "\"
+  //                         %d93-126        ;  or the quote character
+  //
+  // quoted-pair     =       ("\" text) / obs-qp
+  // qcontent        =       qtext / quoted-pair
+  //
+  //
+  // Domain = (sub-domain 1*("." sub-domain)) / address-literal
+  // sub-domain = Let-dig [Ldh-str]
+  //
+  // Let-dig = ALPHA / DIGIT
+  // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
+  //
+  // address-literal = "[" IPv4-address-literal /
+  //                       IPv6-address-literal /
+  //                       General-address-literal "]"
+  //       ; See section 4.1.3
+
+  // However, no one actually implements all that. Known implementations just
+  // do string comparisons, but that is technically incorrect. (Ex: a
+  // constraint excluding |foo@example.com| should exclude a SAN of
+  // |"foo"@example.com|, while a naive direct comparison will allow it.)
+  //
+  // We don't implement all that either, but do something a bit more fail-safe
+  // by rejecting any addresses that contain characters that are not allowed in
+  // the non-quoted formats.
+
+  std::vector<std::string_view> name_components =
+      net::string_util::SplitString(name, '@');
+  if (name_components.size() != 2) {
+    // If we did the full parsing then it is possible for a @ to be in a quoted
+    // local-part of the name, but we don't do that, so just fail if @ appears
+    // more than once.
+    return false;
+  }
+  if (!IsAllowedRfc822LocalPart(name_components[0]) ||
+      !IsAllowedRfc822Domain(name_components[1])) {
+    return false;
+  }
+
+  for (const auto& excluded_name : excluded_subtrees_.rfc822_names) {
+    if (Rfc822NameMatches(name_components[0], name_components[1], excluded_name,
+                          Rfc822NameMatchType::kExcluded,
+                          case_insensitive_exclude_localpart)) {
+      return false;
+    }
+  }
+
+  // If permitted subtrees are not constrained, any name that is not excluded is
+  // allowed.
+  if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_RFC822_NAME)) {
+    return true;
+  }
+
+  for (const auto& permitted_name : permitted_subtrees_.rfc822_names) {
+    if (Rfc822NameMatches(name_components[0], name_components[1],
+                          permitted_name, Rfc822NameMatchType::kPermitted,
+                          /*case_insenitive_local_part=*/false)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 bool NameConstraints::IsPermittedDNSName(std::string_view name) const {
   for (const auto& excluded_name : excluded_subtrees_.dns_names) {
     // When matching wildcard hosts against excluded subtrees, consider it a
diff --git a/net/cert/pki/name_constraints.h b/net/cert/pki/name_constraints.h
index ea472a0..7690c89 100644
--- a/net/cert/pki/name_constraints.h
+++ b/net/cert/pki/name_constraints.h
@@ -50,6 +50,12 @@
                        const GeneralNames* subject_alt_names,
                        CertErrors* errors) const;
 
+  // Returns true if the ASCII email address |name| is permitted. |name| should
+  // be a "mailbox" as specified by RFC 2821, with the additional restriction
+  // that quoted names and whitespace are not allowed by this implementation.
+  bool IsPermittedRfc822Name(std::string_view name,
+                             bool case_insensitive_exclude_localpart) const;
+
   // Returns true if the ASCII hostname |name| is permitted.
   // |name| may be a wildcard hostname (starts with "*."). Eg, "*.bar.com"
   // would not be permitted if "bar.com" is permitted and "foo.bar.com" is
diff --git a/net/cert/pki/name_constraints_unittest.cc b/net/cert/pki/name_constraints_unittest.cc
index b69a376..b8dd0ca 100644
--- a/net/cert/pki/name_constraints_unittest.cc
+++ b/net/cert/pki/name_constraints_unittest.cc
@@ -783,18 +783,57 @@
       der::Input(&constraints_der), is_critical(), &errors));
   ASSERT_TRUE(name_constraints);
 
-  if (is_critical()) {
-    EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
-              name_constraints->constrained_name_types());
-  } else {
-    EXPECT_EQ(0, name_constraints->constrained_name_types());
-  }
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
 
   std::string san_der;
   std::unique_ptr<GeneralNames> san;
+
   ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
-  EXPECT_EQ(!is_critical(),
-            IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-domaincase.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-localpartcase.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-no-at.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-two-ats.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-quoted.pem", &san, &san_der));
+  // `"foo"@example.com` and `foo@example.com` are the same address, but we
+  // don't support quoted address at all.
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-ipv4.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-multiple.pem", &san, &san_der));
+  // SAN contains multiple email addresses, only the first matches the
+  // permitted constraint.
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
 }
 
 TEST_P(ParseNameConstraints, Rfc822NamesInExcluded) {
@@ -806,18 +845,498 @@
       der::Input(&constraints_der), is_critical(), &errors));
   ASSERT_TRUE(name_constraints);
 
-  if (is_critical()) {
-    EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
-              name_constraints->constrained_name_types());
-  } else {
-    EXPECT_EQ(0, name_constraints->constrained_name_types());
-  }
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
 
   std::string san_der;
   std::unique_ptr<GeneralNames> san;
+
   ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
-  EXPECT_EQ(!is_critical(),
-            IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-domaincase.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-localpartcase.pem", &san,
+                                     &san_der));
+  // Excluded names are matched case-sensitive in the local-part for addresses
+  // from subjectAlternativeName, so this is allowed.
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-no-at.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-two-ats.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-ipv4.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameHostnameInPermitted) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-hostname.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-domaincase.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-localpartcase.pem", &san,
+                                     &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-no-at.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-two-ats.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-empty-localpart.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-quoted.pem", &san, &san_der));
+  // `"foo"@example.com` would match `example.com` hostname, but we don't
+  // support quoted address at all.
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-multiple.pem", &san, &san_der));
+  // SAN contains multiple email addresses, all match the permitted hostname.
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameHostnameInExcluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-excluded-hostname.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-domaincase.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-localpartcase.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-no-at.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-two-ats.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-empty-localpart.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameHostnameWithAtInPermitted) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-hostnamewithat.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-empty-localpart.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-domaincase.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-localpartcase.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-no-at.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-two-ats.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameHostnameWithAtInExcluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-excluded-hostnamewithat.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-empty-localpart.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-domaincase.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-localpartcase.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-no-at.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-two-ats.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameSubdomainInPermitted) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-subdomains.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-subdomaincase.pem", &san,
+                                     &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-subdomain-no-at.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-subdomain-two-ats.pem",
+                                     &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameSubdomainInExcluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-excluded-subdomains.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-subdomaincase.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-subdomain-no-at.pem", &san,
+                                     &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name-subdomain-two-ats.pem",
+                                     &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameEmptyPermitted) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-empty.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-empty.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameEmptyExcluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-excluded-empty.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-empty.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameIPv4Permitted) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-ipv4.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-empty.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-ipv4.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, Rfc822NameIPv4Excluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(
+      LoadTestNameConstraint("rfc822name-excluded-ipv4.pem", &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  EXPECT_EQ(GENERAL_NAME_RFC822_NAME,
+            name_constraints->constrained_name_types());
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+
+  ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-empty.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-ipv4.pem", &san, &san_der));
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, QuotedRfc822SanWithNoRfc822Constraints) {
+  // Load an unrelated (non-rfc822) constraint.
+  std::string constraints_der;
+  ASSERT_TRUE(
+      LoadTestNameConstraint("othername-excluded.pem", &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-quoted.pem", &san, &san_der));
+  // A rfc822 in SAN with quotes should be allowed since we only try to parse
+  // the name if we are enforcing a constraint against it.
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, QuotedRfc822SanMatchesQuotedPermitted) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-quoted.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-quoted.pem", &san, &san_der));
+  // Both SAN and constraint are `"foo"@example.com`, but we don't support
+  // quoted address at all.
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
+}
+
+TEST_P(ParseNameConstraints, UnquotedRfc822SanNotMatchingQuotedExcluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-excluded-quoted.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  std::string san_der;
+  std::unique_ptr<GeneralNames> san;
+  ASSERT_TRUE(
+      LoadTestSubjectAltName("san-rfc822name-subdomain.pem", &san, &san_der));
+  // The name `foo@subdomain.example.com` should be allowed since it doesn't
+  // match an exclude of `"foo"@example.com`, but we don't support quoted
+  // address at all so this is not allowed.
+  EXPECT_FALSE(
+      IsPermittedCert(name_constraints.get(), der::Input(), san.get()));
 }
 
 TEST_P(ParseNameConstraints, X400AddresssInPermitted) {
@@ -1085,7 +1604,8 @@
                                        is_critical(), &errors));
 }
 
-TEST_P(ParseNameConstraints, IsPermittedCertSubjectEmailAddressIsOk) {
+TEST_P(ParseNameConstraints,
+       IsPermittedCertSubjectEmailAddressNoEmailConstraint) {
   std::string constraints_der;
   ASSERT_TRUE(LoadTestNameConstraint("directoryname.pem", &constraints_der));
   CertErrors errors;
@@ -1093,15 +1613,53 @@
       der::Input(&constraints_der), is_critical(), &errors));
   ASSERT_TRUE(name_constraints);
 
-  std::string name_us_arizona_email;
-  ASSERT_TRUE(
-      LoadTestName("name-us-arizona-email.pem", &name_us_arizona_email));
-
+  std::string name;
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email.pem", &name));
   // Name constraints don't contain rfc822Name, so emailAddress in subject is
   // allowed regardless.
   EXPECT_TRUE(IsPermittedCert(name_constraints.get(),
-                              SequenceValueFromString(&name_us_arizona_email),
-                              nullptr /* subject_alt_names */));
+                              SequenceValueFromString(&name),
+                              /*subject_alt_names=*/nullptr));
+
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-invalidstring.pem", &name));
+  // Name constraints don't contain rfc822Name, so emailAddress in subject is
+  // allowed regardless.
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(),
+                              SequenceValueFromString(&name),
+                              /*subject_alt_names=*/nullptr));
+}
+
+TEST_P(ParseNameConstraints, IsPermittedCertSubjectEmailAddressIsOk) {
+  std::string constraints_der;
+  ASSERT_TRUE(LoadTestNameConstraint("rfc822name-permitted-hostname.pem",
+                                     &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  std::string name;
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email.pem", &name));
+
+  // Name constraints contain rfc822Name, and the address matches the
+  // constraint (which is all addresses on the hostname.)
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(),
+                              SequenceValueFromString(&name),
+                              /*subject_alt_names=*/nullptr));
+
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-invalidstring.pem", &name));
+  // The bytes of the name string match, but the string type is VISIBLESTRING
+  // which is not supported, so this should fail.
+  EXPECT_FALSE(IsPermittedCert(name_constraints.get(),
+                               SequenceValueFromString(&name),
+                               /*subject_alt_names=*/nullptr));
+
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-multiple.pem", &name));
+  // Subject contains multiple rfc822Names, and they all match the constraint
+  // (which is all addresses on the hostname.)
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(),
+                              SequenceValueFromString(&name),
+                              /*subject_alt_names=*/nullptr));
 }
 
 TEST_P(ParseNameConstraints, IsPermittedCertSubjectEmailAddressIsNotOk) {
@@ -1113,16 +1671,61 @@
       der::Input(&constraints_der), is_critical(), &errors));
   ASSERT_TRUE(name_constraints);
 
-  std::string name_us_arizona_email;
-  ASSERT_TRUE(
-      LoadTestName("name-us-arizona-email.pem", &name_us_arizona_email));
+  std::string name;
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email.pem", &name));
 
-  // Name constraints contain rfc822Name, so emailAddress in subject is not
-  // allowed if the constraints were critical.
-  EXPECT_EQ(!is_critical(),
-            IsPermittedCert(name_constraints.get(),
-                            SequenceValueFromString(&name_us_arizona_email),
-                            nullptr /* subject_alt_names */));
+  // Name constraints contain rfc822Name, and the address does not match the
+  // constraint.
+  EXPECT_FALSE(IsPermittedCert(name_constraints.get(),
+                               SequenceValueFromString(&name),
+                               /*subject_alt_names=*/nullptr));
+
+  // Address is a case-insensitive match, but name constraints (permitted) are
+  // case-sensitive, so this fails.
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-localpartcase.pem", &name));
+  EXPECT_FALSE(IsPermittedCert(name_constraints.get(),
+                               SequenceValueFromString(&name),
+                               /*subject_alt_names=*/nullptr));
+
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-multiple.pem", &name));
+  // Subject contains multiple rfc822Names, and only the first one matches the
+  // constraint.
+  EXPECT_FALSE(IsPermittedCert(name_constraints.get(),
+                               SequenceValueFromString(&name),
+                               /*subject_alt_names=*/nullptr));
+}
+
+TEST_P(ParseNameConstraints, IsPermittedCertSubjectEmailAddressExcluded) {
+  std::string constraints_der;
+  ASSERT_TRUE(
+      LoadTestNameConstraint("rfc822name-excluded.pem", &constraints_der));
+  CertErrors errors;
+  std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create(
+      der::Input(&constraints_der), is_critical(), &errors));
+  ASSERT_TRUE(name_constraints);
+
+  std::string name;
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email.pem", &name));
+
+  // Name constraints contain excluded rfc822Name, and the address does not
+  // match the constraint.
+  EXPECT_TRUE(IsPermittedCert(name_constraints.get(),
+                              SequenceValueFromString(&name),
+                              /*subject_alt_names=*/nullptr));
+
+  // Name constraints for excluded are done case-insensitive in the local part,
+  // so this is not allowed.
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-localpartcase.pem", &name));
+  EXPECT_FALSE(IsPermittedCert(name_constraints.get(),
+                               SequenceValueFromString(&name),
+                               /*subject_alt_names=*/nullptr));
+
+  ASSERT_TRUE(LoadTestName("name-us-arizona-email-multiple.pem", &name));
+  // Subject contains multiple rfc822Names, and one of them is excluded by the
+  // constraint.
+  EXPECT_FALSE(IsPermittedCert(name_constraints.get(),
+                               SequenceValueFromString(&name),
+                               /*subject_alt_names=*/nullptr));
 }
 
 // Hostname in commonName is not allowed (crbug.com/308330), so these are tests
diff --git a/net/cert/pki/nist_pkits_unittest.h b/net/cert/pki/nist_pkits_unittest.h
index 8e4c2cb..45e3ff9 100644
--- a/net/cert/pki/nist_pkits_unittest.h
+++ b/net/cert/pki/nist_pkits_unittest.h
@@ -114,14 +114,6 @@
     //   4.1.4 - Valid DSA Signatures Test4
     //   4.1.5 - Valid DSA Parameter Inheritance Test5
     //
-    // Expected to fail because Name constraints on rfc822Names are not
-    // supported:
-    //
-    //   4.13.21 - Valid RFC822 nameConstraints Test21
-    //   4.13.23 - Valid RFC822 nameConstraints Test23
-    //   4.13.25 - Valid RFC822 nameConstraints Test25
-    //   4.13.27 - Valid DN and RFC822 nameConstraints Test27
-    //
     // Expected to fail because Name constraints on
     // uniformResourceIdentifiers are not supported:
     //
@@ -137,9 +129,7 @@
       modified_info.user_constrained_policy_set = {};
       modified_info.should_validate = false;
       PkitsTestDelegate::RunTest(cert_ders, crl_ders, modified_info);
-    } else if (test_number == "4.13.21" || test_number == "4.13.23" ||
-               test_number == "4.13.25" || test_number == "4.13.27" ||
-               test_number == "4.13.34" || test_number == "4.13.36") {
+    } else if (test_number == "4.13.34" || test_number == "4.13.36") {
       PkitsTestInfo modified_info = info;
       modified_info.should_validate = false;
       PkitsTestDelegate::RunTest(cert_ders, crl_ders, modified_info);
diff --git a/net/cert/pki/verify_name_match.cc b/net/cert/pki/verify_name_match.cc
index 9fa1043..b92c4eb 100644
--- a/net/cert/pki/verify_name_match.cc
+++ b/net/cert/pki/verify_name_match.cc
@@ -387,10 +387,12 @@
                                  SUBTREE_MATCH);
 }
 
-bool NameContainsEmailAddress(const der::Input& name_rdn_sequence,
-                              bool* contained_email_address) {
-  der::Parser rdn_sequence_parser(name_rdn_sequence);
+bool FindEmailAddressesInName(
+    const der::Input& name_rdn_sequence,
+    std::vector<std::string>* contained_email_addresses) {
+  contained_email_addresses->clear();
 
+  der::Parser rdn_sequence_parser(name_rdn_sequence);
   while (rdn_sequence_parser.HasMore()) {
     der::Parser rdn_parser;
     if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser))
@@ -402,13 +404,15 @@
 
     for (const auto& type_and_value : type_and_values) {
       if (type_and_value.type == der::Input(kTypeEmailAddressOid)) {
-        *contained_email_address = true;
-        return true;
+        std::string email_address;
+        if (!type_and_value.ValueAsString(&email_address)) {
+          return false;
+        }
+        contained_email_addresses->push_back(std::move(email_address));
       }
     }
   }
 
-  *contained_email_address = false;
   return true;
 }
 
diff --git a/net/cert/pki/verify_name_match.h b/net/cert/pki/verify_name_match.h
index a288aeb..d22cf7f 100644
--- a/net/cert/pki/verify_name_match.h
+++ b/net/cert/pki/verify_name_match.h
@@ -44,12 +44,16 @@
 
 // Helper functions:
 
-// Checks if |name_rdn_sequence| contains an emailAddress attribute type.
-// If the return value is true, |*contained_email_address| will be set to
-// indicate whether an emailAddress attribute was present.
+// Find all emailAddress attribute values in |name_rdn_sequence|.
+// Returns true if parsing was successful, in which case
+// |*contained_email_address| will contain zero or more values.  The values
+// returned in |*contained_email_addresses| will be UTF8 strings and have been
+// checked that they were valid strings for the string type of the attribute
+// tag, but otherwise have not been validated.
 // Returns false if there was a parsing error.
-[[nodiscard]] bool NameContainsEmailAddress(const der::Input& name_rdn_sequence,
-                                            bool* contained_email_address);
+[[nodiscard]] bool FindEmailAddressesInName(
+    const der::Input& name_rdn_sequence,
+    std::vector<std::string>* contained_email_addresses);
 
 }  // namespace net
 
diff --git a/net/data/name_constraints_unittest/generate_name_constraints.py b/net/data/name_constraints_unittest/generate_name_constraints.py
index 1ac0600..bf7e89b 100755
--- a/net/data/name_constraints_unittest/generate_name_constraints.py
+++ b/net/data/name_constraints_unittest/generate_name_constraints.py
@@ -377,6 +377,23 @@
                                    'bar@example.com')
   generate(n_us_az_email, "name-us-arizona-email.pem")
 
+  n_us_az_email = copy.deepcopy(n_us_az)
+  n_us_az_email.add_rdn().add_attr('emailAddress', 'IA5STRING',
+                                   'FoO@example.com')
+  generate(n_us_az_email, "name-us-arizona-email-localpartcase.pem")
+
+  n_us_az_email = copy.deepcopy(n_us_az)
+  n_us_az_email.add_rdn().add_attr('emailAddress', 'IA5STRING',
+                                   'foo@example.com')
+  n_us_az_email.add_rdn().add_attr('emailAddress', 'IA5STRING',
+                                   'bar@example.com')
+  generate(n_us_az_email, "name-us-arizona-email-multiple.pem")
+
+  n_us_az_email = copy.deepcopy(n_us_az)
+  n_us_az_email.add_rdn().add_attr('emailAddress', 'VISIBLESTRING',
+                                   'bar@example.com')
+  generate(n_us_az_email, "name-us-arizona-email-invalidstring.pem")
+
   n_ca = generate_names.NameGenerator()
   n_ca.add_rdn().add_attr('countryName', 'PRINTABLESTRING', 'CA')
   generate(n_ca, "name-ca.pem")
@@ -445,6 +462,59 @@
   generate(san, "san-rfc822name.pem")
 
   san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@eXaMplE.cOm"))
+  generate(san, "san-rfc822name-domaincase.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("FoO@example.com"))
+  generate(san, "san-rfc822name-localpartcase.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name('\\"foo\\"@example.com'))
+  generate(san, "san-rfc822name-quoted.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("@example.com"))
+  generate(san, "san-rfc822name-empty-localpart.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@subdomain.example.com"))
+  generate(san, "san-rfc822name-subdomain.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@sUbdoMAin.exAmPLe.COm"))
+  generate(san, "san-rfc822name-subdomaincase.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("example.com"))
+  generate(san, "san-rfc822name-no-at.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@bar@example.com"))
+  generate(san, "san-rfc822name-two-ats.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("subdomain.example.com"))
+  generate(san, "san-rfc822name-subdomain-no-at.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@bar@subdomain.example.com"))
+  generate(san, "san-rfc822name-subdomain-two-ats.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name(""))
+  generate(san, "san-rfc822name-empty.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@[8.8.8.8]"))
+  generate(san, "san-rfc822name-ipv4.pem")
+
+  san = SubjectAltNameGenerator()
+  san.add_name(rfc822_name("foo@example.com"))
+  san.add_name(rfc822_name("bar@example.com"))
+  generate(san, "san-rfc822name-multiple.pem")
+
+  san = SubjectAltNameGenerator()
   san.add_name(dns_name("foo.example.com"))
   generate(san, "san-dnsname.pem")
 
@@ -490,11 +560,60 @@
   c = NameConstraintsGenerator()
   c.add_permitted(rfc822_name("foo@example.com"))
   generate(c, "rfc822name-permitted.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_permitted(rfc822_name('\\"foo\\"@example.com'))
+  generate(c, "rfc822name-permitted-quoted.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_permitted(rfc822_name("example.com"))
+  generate(c, "rfc822name-permitted-hostname.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_permitted(rfc822_name("@example.com"))
+  generate(c, "rfc822name-permitted-hostnamewithat.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_permitted(rfc822_name(".example.com"))
+  generate(c, "rfc822name-permitted-subdomains.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_permitted(rfc822_name(""))
+  generate(c, "rfc822name-permitted-empty.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_permitted(rfc822_name("[8.8.8.8]"))
+  generate(c, "rfc822name-permitted-ipv4.pem")
+
   c = NameConstraintsGenerator()
   c.add_excluded(rfc822_name("foo@example.com"))
   generate(c, "rfc822name-excluded.pem")
 
   c = NameConstraintsGenerator()
+  c.add_excluded(rfc822_name('\\"foo\\"@example.com'))
+  generate(c, "rfc822name-excluded-quoted.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_excluded(rfc822_name("example.com"))
+  generate(c, "rfc822name-excluded-hostname.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_excluded(rfc822_name("@example.com"))
+  generate(c, "rfc822name-excluded-hostnamewithat.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_excluded(rfc822_name(".example.com"))
+  generate(c, "rfc822name-excluded-subdomains.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_excluded(rfc822_name(""))
+  generate(c, "rfc822name-excluded-empty.pem")
+
+  c = NameConstraintsGenerator()
+  c.add_excluded(rfc822_name("[8.8.8.8]"))
+  generate(c, "rfc822name-excluded-ipv4.pem")
+
+  c = NameConstraintsGenerator()
   c.add_permitted(x400_address())
   generate(c, "x400address-permitted.pem")
   c = NameConstraintsGenerator()
diff --git a/net/data/name_constraints_unittest/name-us-arizona-email-invalidstring.pem b/net/data/name_constraints_unittest/name-us-arizona-email-invalidstring.pem
new file mode 100644
index 0000000..f9bc413
--- /dev/null
+++ b/net/data/name_constraints_unittest/name-us-arizona-email-invalidstring.pem
@@ -0,0 +1,27 @@
+SEQUENCE {
+  SET {
+    SEQUENCE {
+      # countryName
+      OBJECT_IDENTIFIER { 2.5.4.6 }
+      PrintableString { "US" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # stateOrProvinceName
+      OBJECT_IDENTIFIER { 2.5.4.8 }
+      UTF8String { "Arizona" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # emailAddress
+      OBJECT_IDENTIFIER { 1.2.840.113549.1.9.1 }
+      VisibleString { "bar@example.com" }
+    }
+  }
+}
+-----BEGIN NAME-----
+MD8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMR4wHAYJKoZIhvcNAQkBGg9iYXJAZXhh
+bXBsZS5jb20=
+-----END NAME-----
diff --git a/net/data/name_constraints_unittest/name-us-arizona-email-localpartcase.pem b/net/data/name_constraints_unittest/name-us-arizona-email-localpartcase.pem
new file mode 100644
index 0000000..2ee64d9
--- /dev/null
+++ b/net/data/name_constraints_unittest/name-us-arizona-email-localpartcase.pem
@@ -0,0 +1,27 @@
+SEQUENCE {
+  SET {
+    SEQUENCE {
+      # countryName
+      OBJECT_IDENTIFIER { 2.5.4.6 }
+      PrintableString { "US" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # stateOrProvinceName
+      OBJECT_IDENTIFIER { 2.5.4.8 }
+      UTF8String { "Arizona" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # emailAddress
+      OBJECT_IDENTIFIER { 1.2.840.113549.1.9.1 }
+      IA5String { "FoO@example.com" }
+    }
+  }
+}
+-----BEGIN NAME-----
+MD8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMR4wHAYJKoZIhvcNAQkBFg9Gb09AZXhh
+bXBsZS5jb20=
+-----END NAME-----
diff --git a/net/data/name_constraints_unittest/name-us-arizona-email-multiple.pem b/net/data/name_constraints_unittest/name-us-arizona-email-multiple.pem
new file mode 100644
index 0000000..b5add4a
--- /dev/null
+++ b/net/data/name_constraints_unittest/name-us-arizona-email-multiple.pem
@@ -0,0 +1,34 @@
+SEQUENCE {
+  SET {
+    SEQUENCE {
+      # countryName
+      OBJECT_IDENTIFIER { 2.5.4.6 }
+      PrintableString { "US" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # stateOrProvinceName
+      OBJECT_IDENTIFIER { 2.5.4.8 }
+      UTF8String { "Arizona" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # emailAddress
+      OBJECT_IDENTIFIER { 1.2.840.113549.1.9.1 }
+      IA5String { "foo@example.com" }
+    }
+  }
+  SET {
+    SEQUENCE {
+      # emailAddress
+      OBJECT_IDENTIFIER { 1.2.840.113549.1.9.1 }
+      IA5String { "bar@example.com" }
+    }
+  }
+}
+-----BEGIN NAME-----
+MF8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMR4wHAYJKoZIhvcNAQkBFg9mb29AZXhh
+bXBsZS5jb20xHjAcBgkqhkiG9w0BCQEWD2JhckBleGFtcGxlLmNvbQ==
+-----END NAME-----
diff --git a/net/data/name_constraints_unittest/rfc822name-excluded-empty.pem b/net/data/name_constraints_unittest/rfc822name-excluded-empty.pem
new file mode 100644
index 0000000..ab98f4a
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-excluded-empty.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [1] {
+    SEQUENCE {
+      [1 PRIMITIVE] {}
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MAahBDACgQA=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-excluded-hostname.pem b/net/data/name_constraints_unittest/rfc822name-excluded-hostname.pem
new file mode 100644
index 0000000..ee7f713
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-excluded-hostname.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [1] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBGhDzANgQtleGFtcGxlLmNvbQ==
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-excluded-hostnamewithat.pem b/net/data/name_constraints_unittest/rfc822name-excluded-hostnamewithat.pem
new file mode 100644
index 0000000..c1cf80f
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-excluded-hostnamewithat.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [1] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "@example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBKhEDAOgQxAZXhhbXBsZS5jb20=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-excluded-ipv4.pem b/net/data/name_constraints_unittest/rfc822name-excluded-ipv4.pem
new file mode 100644
index 0000000..788b7fe
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-excluded-ipv4.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [1] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "[8.8.8.8]" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MA+hDTALgQlbOC44LjguOF0=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-excluded-quoted.pem b/net/data/name_constraints_unittest/rfc822name-excluded-quoted.pem
new file mode 100644
index 0000000..ddad9b88
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-excluded-quoted.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [1] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "\"foo\"@example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBehFTATgREiZm9vIkBleGFtcGxlLmNvbQ==
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-excluded-subdomains.pem b/net/data/name_constraints_unittest/rfc822name-excluded-subdomains.pem
new file mode 100644
index 0000000..964cfd12
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-excluded-subdomains.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [1] {
+    SEQUENCE {
+      [1 PRIMITIVE] { ".example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBKhEDAOgQwuZXhhbXBsZS5jb20=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-permitted-empty.pem b/net/data/name_constraints_unittest/rfc822name-permitted-empty.pem
new file mode 100644
index 0000000..12e9e37
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-permitted-empty.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [0] {
+    SEQUENCE {
+      [1 PRIMITIVE] {}
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MAagBDACgQA=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-permitted-hostname.pem b/net/data/name_constraints_unittest/rfc822name-permitted-hostname.pem
new file mode 100644
index 0000000..56fc8eb
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-permitted-hostname.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [0] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBGgDzANgQtleGFtcGxlLmNvbQ==
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-permitted-hostnamewithat.pem b/net/data/name_constraints_unittest/rfc822name-permitted-hostnamewithat.pem
new file mode 100644
index 0000000..c1be142
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-permitted-hostnamewithat.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [0] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "@example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBKgEDAOgQxAZXhhbXBsZS5jb20=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-permitted-ipv4.pem b/net/data/name_constraints_unittest/rfc822name-permitted-ipv4.pem
new file mode 100644
index 0000000..46a3207
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-permitted-ipv4.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [0] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "[8.8.8.8]" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MA+gDTALgQlbOC44LjguOF0=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-permitted-quoted.pem b/net/data/name_constraints_unittest/rfc822name-permitted-quoted.pem
new file mode 100644
index 0000000..858a0281
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-permitted-quoted.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [0] {
+    SEQUENCE {
+      [1 PRIMITIVE] { "\"foo\"@example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBegFTATgREiZm9vIkBleGFtcGxlLmNvbQ==
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/rfc822name-permitted-subdomains.pem b/net/data/name_constraints_unittest/rfc822name-permitted-subdomains.pem
new file mode 100644
index 0000000..c21de69
--- /dev/null
+++ b/net/data/name_constraints_unittest/rfc822name-permitted-subdomains.pem
@@ -0,0 +1,10 @@
+SEQUENCE {
+  [0] {
+    SEQUENCE {
+      [1 PRIMITIVE] { ".example.com" }
+    }
+  }
+}
+-----BEGIN NAME CONSTRAINTS-----
+MBKgEDAOgQwuZXhhbXBsZS5jb20=
+-----END NAME CONSTRAINTS-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-domaincase.pem b/net/data/name_constraints_unittest/san-rfc822name-domaincase.pem
new file mode 100644
index 0000000..d14130c
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-domaincase.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@eXaMplE.cOm" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBGBD2Zvb0BlWGFNcGxFLmNPbQ==
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-empty-localpart.pem b/net/data/name_constraints_unittest/san-rfc822name-empty-localpart.pem
new file mode 100644
index 0000000..857b746
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-empty-localpart.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "@example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MA6BDEBleGFtcGxlLmNvbQ==
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-empty.pem b/net/data/name_constraints_unittest/san-rfc822name-empty.pem
new file mode 100644
index 0000000..851dfc0
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-empty.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] {}
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MAKBAA==
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-ipv4.pem b/net/data/name_constraints_unittest/san-rfc822name-ipv4.pem
new file mode 100644
index 0000000..6c2861fb
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-ipv4.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@[8.8.8.8]" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MA+BDWZvb0BbOC44LjguOF0=
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-localpartcase.pem b/net/data/name_constraints_unittest/san-rfc822name-localpartcase.pem
new file mode 100644
index 0000000..7ab5a23b
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-localpartcase.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "FoO@example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBGBD0ZvT0BleGFtcGxlLmNvbQ==
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-multiple.pem b/net/data/name_constraints_unittest/san-rfc822name-multiple.pem
new file mode 100644
index 0000000..48840c9
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-multiple.pem
@@ -0,0 +1,7 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@example.com" }
+  [1 PRIMITIVE] { "bar@example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MCKBD2Zvb0BleGFtcGxlLmNvbYEPYmFyQGV4YW1wbGUuY29t
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-no-at.pem b/net/data/name_constraints_unittest/san-rfc822name-no-at.pem
new file mode 100644
index 0000000..175af6f
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-no-at.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MA2BC2V4YW1wbGUuY29t
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-quoted.pem b/net/data/name_constraints_unittest/san-rfc822name-quoted.pem
new file mode 100644
index 0000000..1bcc4bd
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-quoted.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "\"foo\"@example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBOBESJmb28iQGV4YW1wbGUuY29t
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-subdomain-no-at.pem b/net/data/name_constraints_unittest/san-rfc822name-subdomain-no-at.pem
new file mode 100644
index 0000000..4e56bc2
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-subdomain-no-at.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "subdomain.example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBeBFXN1YmRvbWFpbi5leGFtcGxlLmNvbQ==
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-subdomain-two-ats.pem b/net/data/name_constraints_unittest/san-rfc822name-subdomain-two-ats.pem
new file mode 100644
index 0000000..5acd05a
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-subdomain-two-ats.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@bar@subdomain.example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MB+BHWZvb0BiYXJAc3ViZG9tYWluLmV4YW1wbGUuY29t
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-subdomain.pem b/net/data/name_constraints_unittest/san-rfc822name-subdomain.pem
new file mode 100644
index 0000000..b520559
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-subdomain.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@subdomain.example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBuBGWZvb0BzdWJkb21haW4uZXhhbXBsZS5jb20=
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-subdomaincase.pem b/net/data/name_constraints_unittest/san-rfc822name-subdomaincase.pem
new file mode 100644
index 0000000..eede4fb2
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-subdomaincase.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@sUbdoMAin.exAmPLe.COm" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBuBGWZvb0BzVWJkb01BaW4uZXhBbVBMZS5DT20=
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/name_constraints_unittest/san-rfc822name-two-ats.pem b/net/data/name_constraints_unittest/san-rfc822name-two-ats.pem
new file mode 100644
index 0000000..421b348
--- /dev/null
+++ b/net/data/name_constraints_unittest/san-rfc822name-two-ats.pem
@@ -0,0 +1,6 @@
+SEQUENCE {
+  [1 PRIMITIVE] { "foo@bar@example.com" }
+}
+-----BEGIN SUBJECT ALTERNATIVE NAME-----
+MBWBE2Zvb0BiYXJAZXhhbXBsZS5jb20=
+-----END SUBJECT ALTERNATIVE NAME-----
diff --git a/net/data/test_bundle_data.filelist b/net/data/test_bundle_data.filelist
index 19fd1c3..65cf736 100644
--- a/net/data/test_bundle_data.filelist
+++ b/net/data/test_bundle_data.filelist
@@ -359,6 +359,9 @@
 data/name_constraints_unittest/name-jp.pem
 data/name_constraints_unittest/name-us-arizona-1.1.1.1.pem
 data/name_constraints_unittest/name-us-arizona-192.168.1.1.pem
+data/name_constraints_unittest/name-us-arizona-email-invalidstring.pem
+data/name_constraints_unittest/name-us-arizona-email-localpartcase.pem
+data/name_constraints_unittest/name-us-arizona-email-multiple.pem
 data/name_constraints_unittest/name-us-arizona-email.pem
 data/name_constraints_unittest/name-us-arizona-foo.com.pem
 data/name_constraints_unittest/name-us-arizona-ipv6.pem
@@ -373,7 +376,19 @@
 data/name_constraints_unittest/othername-permitted.pem
 data/name_constraints_unittest/registeredid-excluded.pem
 data/name_constraints_unittest/registeredid-permitted.pem
+data/name_constraints_unittest/rfc822name-excluded-empty.pem
+data/name_constraints_unittest/rfc822name-excluded-hostname.pem
+data/name_constraints_unittest/rfc822name-excluded-hostnamewithat.pem
+data/name_constraints_unittest/rfc822name-excluded-ipv4.pem
+data/name_constraints_unittest/rfc822name-excluded-quoted.pem
+data/name_constraints_unittest/rfc822name-excluded-subdomains.pem
 data/name_constraints_unittest/rfc822name-excluded.pem
+data/name_constraints_unittest/rfc822name-permitted-empty.pem
+data/name_constraints_unittest/rfc822name-permitted-hostname.pem
+data/name_constraints_unittest/rfc822name-permitted-hostnamewithat.pem
+data/name_constraints_unittest/rfc822name-permitted-ipv4.pem
+data/name_constraints_unittest/rfc822name-permitted-quoted.pem
+data/name_constraints_unittest/rfc822name-permitted-subdomains.pem
 data/name_constraints_unittest/rfc822name-permitted.pem
 data/name_constraints_unittest/san-directoryname.pem
 data/name_constraints_unittest/san-dnsname.pem
@@ -388,6 +403,19 @@
 data/name_constraints_unittest/san-othername.pem
 data/name_constraints_unittest/san-permitted.pem
 data/name_constraints_unittest/san-registeredid.pem
+data/name_constraints_unittest/san-rfc822name-domaincase.pem
+data/name_constraints_unittest/san-rfc822name-empty-localpart.pem
+data/name_constraints_unittest/san-rfc822name-empty.pem
+data/name_constraints_unittest/san-rfc822name-ipv4.pem
+data/name_constraints_unittest/san-rfc822name-localpartcase.pem
+data/name_constraints_unittest/san-rfc822name-multiple.pem
+data/name_constraints_unittest/san-rfc822name-no-at.pem
+data/name_constraints_unittest/san-rfc822name-quoted.pem
+data/name_constraints_unittest/san-rfc822name-subdomain-no-at.pem
+data/name_constraints_unittest/san-rfc822name-subdomain-two-ats.pem
+data/name_constraints_unittest/san-rfc822name-subdomain.pem
+data/name_constraints_unittest/san-rfc822name-subdomaincase.pem
+data/name_constraints_unittest/san-rfc822name-two-ats.pem
 data/name_constraints_unittest/san-rfc822name.pem
 data/name_constraints_unittest/san-uri.pem
 data/name_constraints_unittest/san-x400address.pem
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 997499a..525520c 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -588,9 +588,8 @@
       // that differ only in their (non-singleton) `query_types` fields. When we
       // enable new query types, this behavior could lead to subtle bugs. That
       // is why the following DCHECK restricts the allowable query types.
-      DCHECK(Difference(query_types,
-                        DnsQueryTypeSet(DnsQueryType::A, DnsQueryType::AAAA,
-                                        DnsQueryType::HTTPS))
+      DCHECK(Difference(query_types, {DnsQueryType::A, DnsQueryType::AAAA,
+                                      DnsQueryType::HTTPS})
                  .Empty());
     }
     const DnsQueryType query_type_for_key = query_types.Size() == 1
@@ -3867,11 +3866,11 @@
   *out_effective_flags = flags | additional_resolver_flags_;
 
   if (dns_query_type != DnsQueryType::UNSPECIFIED) {
-    *out_effective_types = dns_query_type;
+    *out_effective_types = {dns_query_type};
     return;
   }
 
-  DnsQueryTypeSet effective_types(DnsQueryType::A, DnsQueryType::AAAA);
+  DnsQueryTypeSet effective_types = {DnsQueryType::A, DnsQueryType::AAAA};
 
   // Disable AAAA queries when we cannot do anything with the results.
   bool use_local_ipv6 = true;
diff --git a/net/dns/host_resolver_mdns_task.cc b/net/dns/host_resolver_mdns_task.cc
index 9958faa..edd2b3f 100644
--- a/net/dns/host_resolver_mdns_task.cc
+++ b/net/dns/host_resolver_mdns_task.cc
@@ -137,7 +137,7 @@
   DCHECK(!query_types.Empty());
   DCHECK(!query_types.Has(DnsQueryType::UNSPECIFIED));
 
-  static constexpr DnsQueryTypeSet kUnwantedQueries(DnsQueryType::HTTPS);
+  static constexpr DnsQueryTypeSet kUnwantedQueries = {DnsQueryType::HTTPS};
 
   for (DnsQueryType query_type : Difference(query_types, kUnwantedQueries))
     transactions_.emplace_back(query_type, this);
diff --git a/net/dns/mock_host_resolver.cc b/net/dns/mock_host_resolver.cc
index f49726b..b4219b6 100644
--- a/net/dns/mock_host_resolver.cc
+++ b/net/dns/mock_host_resolver.cc
@@ -1058,7 +1058,7 @@
   state_->IncrementNumNonLocalResolves();
 
   const RuleResolver::RuleResultOrError& result = rule_resolver_.Resolve(
-      request.request_endpoint(), request.parameters().dns_query_type,
+      request.request_endpoint(), {request.parameters().dns_query_type},
       request.parameters().source);
 
   int error = ERR_UNEXPECTED;
diff --git a/net/quic/dedicated_web_transport_http3_client.cc b/net/quic/dedicated_web_transport_http3_client.cc
index 97bc56a..0de9441 100644
--- a/net/quic/dedicated_web_transport_http3_client.cc
+++ b/net/quic/dedicated_web_transport_http3_client.cc
@@ -253,6 +253,35 @@
          state == WebTransportState::FAILED;
 }
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class NegotiatedHttpDatagramVersion {
+  kNone = 0,
+  kDraft04 = 1,
+  kRfc = 2,
+  kMaxValue = kRfc,
+};
+
+void RecordNegotiatedHttpDatagramSupport(quic::HttpDatagramSupport support) {
+  NegotiatedHttpDatagramVersion negotiated;
+  switch (support) {
+    case quic::HttpDatagramSupport::kNone:
+      negotiated = NegotiatedHttpDatagramVersion::kNone;
+      break;
+    case quic::HttpDatagramSupport::kDraft04:
+      negotiated = NegotiatedHttpDatagramVersion::kDraft04;
+      break;
+    case quic::HttpDatagramSupport::kRfc:
+      negotiated = NegotiatedHttpDatagramVersion::kRfc;
+      break;
+    case quic::HttpDatagramSupport::kRfcAndDraft04:
+      NOTREACHED();
+      return;
+  }
+  base::UmaHistogramEnumeration(
+      "Net.WebTransport.NegotiatedHttpDatagramVersion", negotiated);
+}
+
 }  // namespace
 
 DedicatedWebTransportHttp3Client::DedicatedWebTransportHttp3Client(
@@ -727,6 +756,7 @@
 void DedicatedWebTransportHttp3Client::OnSessionReady(
     const spdy::Http2HeaderBlock& /*spdy_headers*/) {
   session_ready_ = true;
+  RecordNegotiatedHttpDatagramSupport(session_->http_datagram_support());
 }
 
 void DedicatedWebTransportHttp3Client::OnSessionClosed(
diff --git a/net/socket/socket_bio_adapter.cc b/net/socket/socket_bio_adapter.cc
index de100ed..59e928e1 100644
--- a/net/socket/socket_bio_adapter.cc
+++ b/net/socket/socket_bio_adapter.cc
@@ -75,16 +75,19 @@
 }
 
 SocketBIOAdapter::~SocketBIOAdapter() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // BIOs are reference-counted and may outlive the adapter. Clear the pointer
   // so future operations fail.
   BIO_set_data(bio_.get(), nullptr);
 }
 
 bool SocketBIOAdapter::HasPendingReadData() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return read_result_ > 0;
 }
 
 size_t SocketBIOAdapter::GetAllocationSize() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   size_t buffer_size = 0;
   if (read_buffer_)
     buffer_size += read_buffer_capacity_;
@@ -95,6 +98,7 @@
 }
 
 int SocketBIOAdapter::BIORead(char* out, int len) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (len <= 0)
     return len;
 
@@ -115,9 +119,10 @@
     // layer reads the record header and body in separate reads to avoid
     // overreading, but issuing one is more efficient. SSL sockets are not
     // reused after shutdown for non-SSL traffic, so overreading is fine.
-    DCHECK(!read_buffer_);
-    DCHECK_EQ(0, read_offset_);
+    CHECK(!read_buffer_);
+    CHECK_EQ(0, read_offset_);
     read_buffer_ = base::MakeRefCounted<IOBuffer>(read_buffer_capacity_);
+    read_result_ = ERR_IO_PENDING;
     int result = socket_->ReadIfReady(
         read_buffer_.get(), read_buffer_capacity_,
         base::BindOnce(&SocketBIOAdapter::OnSocketReadIfReadyComplete,
@@ -128,9 +133,8 @@
       result = socket_->Read(read_buffer_.get(), read_buffer_capacity_,
                              read_callback_);
     }
-    if (result == ERR_IO_PENDING) {
-      read_result_ = ERR_IO_PENDING;
-    } else {
+    if (result != ERR_IO_PENDING) {
+      // `HandleSocketReadResult` will update `read_result_` based on `result`.
       HandleSocketReadResult(result);
     }
   }
@@ -164,7 +168,9 @@
 }
 
 void SocketBIOAdapter::HandleSocketReadResult(int result) {
-  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_NE(ERR_IO_PENDING, result);
+  CHECK_EQ(ERR_IO_PENDING, read_result_);
 
   // If an EOF, canonicalize to ERR_CONNECTION_CLOSED here, so that higher
   // levels don't report success.
@@ -179,15 +185,17 @@
 }
 
 void SocketBIOAdapter::OnSocketReadComplete(int result) {
-  DCHECK_EQ(ERR_IO_PENDING, read_result_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(ERR_IO_PENDING, read_result_);
 
   HandleSocketReadResult(result);
   delegate_->OnReadReady();
 }
 
 void SocketBIOAdapter::OnSocketReadIfReadyComplete(int result) {
-  DCHECK_EQ(ERR_IO_PENDING, read_result_);
-  DCHECK_GE(OK, result);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(ERR_IO_PENDING, read_result_);
+  CHECK_GE(OK, result);
 
   // Do not use HandleSocketReadResult() because result == OK doesn't mean EOF.
   read_result_ = result;
@@ -196,12 +204,13 @@
 }
 
 int SocketBIOAdapter::BIOWrite(const char* in, int len) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (len <= 0)
     return len;
 
   // If the write buffer is not empty, there must be a pending Write() to flush
   // it.
-  DCHECK(write_buffer_used_ == 0 || write_error_ == ERR_IO_PENDING);
+  CHECK(write_buffer_used_ == 0 || write_error_ == ERR_IO_PENDING);
 
   // If a previous Write() failed, report the error.
   if (write_error_ != OK && write_error_ != ERR_IO_PENDING) {
@@ -211,7 +220,7 @@
 
   // Instantiate the write buffer if needed.
   if (!write_buffer_) {
-    DCHECK_EQ(0, write_buffer_used_);
+    CHECK_EQ(0, write_buffer_used_);
     write_buffer_ = base::MakeRefCounted<GrowableIOBuffer>();
     write_buffer_->SetCapacity(write_buffer_capacity_);
   }
@@ -250,7 +259,7 @@
   }
 
   // Either the buffer is now full or there is no more input.
-  DCHECK(len == 0 || write_buffer_used_ == write_buffer_->capacity());
+  CHECK(len == 0 || write_buffer_used_ == write_buffer_->capacity());
 
   // Schedule a socket Write() if necessary. (The ring buffer may previously
   // have been empty.)
@@ -270,22 +279,30 @@
 }
 
 void SocketBIOAdapter::SocketWrite() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   while (write_error_ == OK && write_buffer_used_ > 0) {
+    int write_buffer_used_old = write_buffer_used_;
     int write_size =
         std::min(write_buffer_used_, write_buffer_->RemainingCapacity());
+    write_error_ = ERR_IO_PENDING;
     int result = socket_->Write(write_buffer_.get(), write_size,
                                 write_callback_, kTrafficAnnotation);
-    if (result == ERR_IO_PENDING) {
-      write_error_ = ERR_IO_PENDING;
-      return;
+    // If `write_buffer_used_` changed across a call to the underlying socket,
+    // something went very wrong.
+    //
+    // TODO(crbug.com/1440692): Remove this once the crash is resolved.
+    CHECK_EQ(write_buffer_used_old, write_buffer_used_);
+    if (result != ERR_IO_PENDING) {
+      // `HandleSocketWriteResult` will update `write_error_` based on `result.
+      HandleSocketWriteResult(result);
     }
-
-    HandleSocketWriteResult(result);
   }
 }
 
 void SocketBIOAdapter::HandleSocketWriteResult(int result) {
-  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_NE(ERR_IO_PENDING, result);
+  CHECK_EQ(ERR_IO_PENDING, write_error_);
 
   if (result < 0) {
     write_error_ = result;
@@ -311,7 +328,8 @@
 }
 
 void SocketBIOAdapter::OnSocketWriteComplete(int result) {
-  DCHECK_EQ(ERR_IO_PENDING, write_error_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(ERR_IO_PENDING, write_error_);
 
   bool was_full = write_buffer_used_ == write_buffer_->capacity();
 
@@ -335,6 +353,7 @@
 }
 
 void SocketBIOAdapter::CallOnReadReady() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (read_result_ == ERR_IO_PENDING)
     delegate_->OnReadReady();
 }
@@ -343,7 +362,7 @@
   SocketBIOAdapter* adapter =
       reinterpret_cast<SocketBIOAdapter*>(BIO_get_data(bio));
   if (adapter) {
-    DCHECK_EQ(bio, adapter->bio());
+    CHECK_EQ(bio, adapter->bio());
   }
   return adapter;
 }
diff --git a/net/socket/socket_bio_adapter.h b/net/socket/socket_bio_adapter.h
index 6ac0cf7..06212a7 100644
--- a/net/socket/socket_bio_adapter.h
+++ b/net/socket/socket_bio_adapter.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "net/base/completion_repeating_callback.h"
 #include "net/base/net_errors.h"
 #include "net/base/net_export.h"
@@ -142,6 +143,7 @@
 
   raw_ptr<Delegate> delegate_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
   base::WeakPtrFactory<SocketBIOAdapter> weak_factory_{this};
 };
 
diff --git a/printing/backend/cups_jobs.cc b/printing/backend/cups_jobs.cc
index ee3f0bef..94c188c 100644
--- a/printing/backend/cups_jobs.cc
+++ b/printing/backend/cups_jobs.cc
@@ -111,6 +111,7 @@
 constexpr char kDeveloperEmpty[] = "developer-empty";
 constexpr char kInterpreterResourceUnavailable[] =
     "interpreter-resource-unavailable";
+constexpr char kCupsPkiExpired[] = "cups-pki-expired";
 
 constexpr char kIppScheme[] = "ipp";
 constexpr char kIppsScheme[] = "ipps";
@@ -195,6 +196,7 @@
           {kDeveloperEmpty, PReason::kDeveloperEmpty},
           {kInterpreterResourceUnavailable,
            PReason::kInterpreterResourceUnavailable},
+          {kCupsPkiExpired, PReason::kCupsPkiExpired},
       });
 
   const auto* entry = kLabelToReasonMap.find(reason);
diff --git a/printing/printer_status.h b/printing/printer_status.h
index 37d21dcc..894ba5e6 100644
--- a/printing/printer_status.h
+++ b/printing/printer_status.h
@@ -56,7 +56,8 @@
       kDeveloperLow = 31,
       kDeveloperEmpty = 32,
       kInterpreterResourceUnavailable = 33,
-      kMaxValue = kInterpreterResourceUnavailable
+      kCupsPkiExpired = 34,
+      kMaxValue = kCupsPkiExpired
     };
 
     // Severity of the state-reason.
diff --git a/remoting/host/desktop_display_info_loader_chromeos_unittest.cc b/remoting/host/desktop_display_info_loader_chromeos_unittest.cc
index da8add5..769a0ac 100644
--- a/remoting/host/desktop_display_info_loader_chromeos_unittest.cc
+++ b/remoting/host/desktop_display_info_loader_chromeos_unittest.cc
@@ -128,7 +128,7 @@
 
 TEST_F(DesktopDisplayInfoLoaderChromeOsTest,
        OriginShouldRespectDeviceScaleFactor) {
-  ash_proxy().AddDisplayFromSpecWithId("10+20-100x100*2", kAnyId);
+  ash_proxy().AddDisplayFromSpecWithId("10+20-1000x500*2", kAnyId);
 
   EXPECT_THAT(CalculateDisplayInfo(), IsSingleDisplayWith(Origin(5, 10)));
 }
diff --git a/services/accessibility/assistive_technology_controller_impl.cc b/services/accessibility/assistive_technology_controller_impl.cc
index c5eca50..5ec26a5 100644
--- a/services/accessibility/assistive_technology_controller_impl.cc
+++ b/services/accessibility/assistive_technology_controller_impl.cc
@@ -69,6 +69,12 @@
   enabled_ATs_[type]->ExecuteScript(script, std::move(on_complete));
 }
 
+void AssistiveTechnologyControllerImpl::SetTestInterface(
+    mojom::AssistiveTechnologyType type,
+    std::unique_ptr<InterfaceBinder> test_interface) {
+  enabled_ATs_[type]->SetTestMojoInterface(std::move(test_interface));
+}
+
 scoped_refptr<V8Manager> AssistiveTechnologyControllerImpl::GetOrMakeV8Manager(
     mojom::AssistiveTechnologyType type) {
   // For the first one we can ask it to initialize v8.
diff --git a/services/accessibility/assistive_technology_controller_impl.h b/services/accessibility/assistive_technology_controller_impl.h
index 9760dce9..09f7308 100644
--- a/services/accessibility/assistive_technology_controller_impl.h
+++ b/services/accessibility/assistive_technology_controller_impl.h
@@ -15,6 +15,7 @@
 
 namespace ax {
 class V8Manager;
+class InterfaceBinder;
 
 // Implementation of the assistive technology controller interface
 // for Chrome OS. This tracks which features are enabled and will
@@ -55,6 +56,8 @@
   void RunScriptForTest(mojom::AssistiveTechnologyType type,
                         const std::string& script,
                         base::OnceClosure on_complete);
+  void SetTestInterface(mojom::AssistiveTechnologyType type,
+                        std::unique_ptr<InterfaceBinder> test_interface);
 
  private:
   scoped_refptr<V8Manager> GetOrMakeV8Manager(
diff --git a/services/accessibility/features/v8_manager.cc b/services/accessibility/features/v8_manager.cc
index 2392db8..8336b0d 100644
--- a/services/accessibility/features/v8_manager.cc
+++ b/services/accessibility/features/v8_manager.cc
@@ -87,6 +87,7 @@
 void V8Manager::InstallAutomation(
     base::WeakPtr<AssistiveTechnologyControllerImpl> at_controller) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
+  DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
   v8_runner_->PostTask(
       FROM_HERE, base::BindOnce(&V8Manager::BindAutomationOnThread,
                                 weak_ptr_factory_.GetWeakPtr(), at_controller));
@@ -94,6 +95,7 @@
 
 void V8Manager::AddV8Bindings() {
   DETACH_FROM_SEQUENCE(sequence_checker_);
+  DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
   v8_runner_->PostTask(FROM_HERE,
                        base::BindOnce(&V8Manager::AddV8BindingsOnThread,
                                       weak_ptr_factory_.GetWeakPtr()));
@@ -102,6 +104,7 @@
 void V8Manager::ExecuteScript(const std::string& script,
                               base::OnceCallback<void()> on_complete) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
+  DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
   v8_runner_->PostTask(FROM_HERE,
                        base::BindOnce(&V8Manager::ExecuteScriptOnThread,
                                       weak_ptr_factory_.GetWeakPtr(), script,
@@ -133,8 +136,12 @@
 
 void V8Manager::SetTestMojoInterface(
     std::unique_ptr<InterfaceBinder> test_interface) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  test_mojo_interface_ = std::move(test_interface);
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+  DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
+  v8_runner_->PostTask(FROM_HERE,
+                       base::BindOnce(&V8Manager::SetTestMojoInterfaceOnThread,
+                                      weak_ptr_factory_.GetWeakPtr(),
+                                      std::move(test_interface)));
 }
 
 void V8Manager::ConstructIsolateOnThread() {
@@ -239,6 +246,12 @@
       weak_ptr_factory_.GetWeakPtr(), at_controller, main_runner_);
 }
 
+void V8Manager::SetTestMojoInterfaceOnThread(
+    std::unique_ptr<InterfaceBinder> test_interface) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  test_mojo_interface_ = std::move(test_interface);
+}
+
 void V8Manager::ExecuteScriptOnThread(const std::string& script,
                                       base::OnceCallback<void()> on_complete) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/services/accessibility/features/v8_manager.h b/services/accessibility/features/v8_manager.h
index b4c4070..fd47a24a 100644
--- a/services/accessibility/features/v8_manager.h
+++ b/services/accessibility/features/v8_manager.h
@@ -93,6 +93,8 @@
   void AddV8BindingsOnThread();
   void BindAutomationOnThread(
       base::WeakPtr<AssistiveTechnologyControllerImpl> at_controller);
+  void SetTestMojoInterfaceOnThread(
+      std::unique_ptr<InterfaceBinder> test_interface);
   void ExecuteScriptOnThread(const std::string& script,
                              base::OnceCallback<void()> on_complete);
 
diff --git a/services/device/compute_pressure/cpu_probe_linux.h b/services/device/compute_pressure/cpu_probe_linux.h
index 1c1481e..d9e30b7 100644
--- a/services/device/compute_pressure/cpu_probe_linux.h
+++ b/services/device/compute_pressure/cpu_probe_linux.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
diff --git a/services/device/compute_pressure/cpu_probe_mac.h b/services/device/compute_pressure/cpu_probe_mac.h
index 332e431..0d948d4 100644
--- a/services/device/compute_pressure/cpu_probe_mac.h
+++ b/services/device/compute_pressure/cpu_probe_mac.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/thread_annotations.h"
 #include "base/threading/sequence_bound.h"
diff --git a/services/device/compute_pressure/cpu_probe_win.h b/services/device/compute_pressure/cpu_probe_win.h
index d7df5aa5..ebcbc885 100644
--- a/services/device/compute_pressure/cpu_probe_win.h
+++ b/services/device/compute_pressure/cpu_probe_win.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/thread_annotations.h"
 #include "base/threading/sequence_bound.h"
diff --git a/services/device/public/cpp/device_feature_list.cc b/services/device/public/cpp/device_feature_list.cc
index 25677491..9a92cec 100644
--- a/services/device/public/cpp/device_feature_list.cc
+++ b/services/device/public/cpp/device_feature_list.cc
@@ -21,6 +21,7 @@
 // in other locations in the code base (e.g. content_features.h).
 const base::Feature* const kFeaturesExposedToJava[] = {
     &device::kWebAuthnAndroidCredMan,
+    &device::kWebAuthnHybridLinkWithoutNotifications,
     &kAsyncSensorCalls,
     &kGenericSensorExtraClasses,
 };
diff --git a/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java b/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java
index 2d4033e..45f02f8a 100644
--- a/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java
+++ b/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java
@@ -17,6 +17,8 @@
     public static final String ASYNC_SENSOR_CALLS = "AsyncSensorCalls";
     public static final String GENERIC_SENSOR_EXTRA_CLASSES = "GenericSensorExtraClasses";
     public static final String WEBAUTHN_ANDROID_CRED_MAN = "WebAuthenticationAndroidCredMan";
+    public static final String WEBAUTHN_HYBRID_LINK_WITHOUT_NOTIFICATIONS =
+            "WebAuthenticationHybridLinkWithoutNotifications";
 
     private DeviceFeatureList() {}
 
diff --git a/services/metrics/public/cpp/ukm_recorder.cc b/services/metrics/public/cpp/ukm_recorder.cc
index 3610400..5c7dc17 100644
--- a/services/metrics/public/cpp/ukm_recorder.cc
+++ b/services/metrics/public/cpp/ukm_recorder.cc
@@ -54,14 +54,6 @@
 }
 
 // static
-ukm::SourceId UkmRecorder::GetSourceIdForDesktopWebAppStartUrl(
-    base::PassKey<web_app::DesktopWebAppUkmRecorder>,
-    const GURL& start_url) {
-  return UkmRecorder::GetSourceIdFromScopeImpl(
-      start_url, SourceIdType::DESKTOP_WEB_APP_ID);
-}
-
-// static
 ukm::SourceId UkmRecorder::GetSourceIdForWebIdentityFromScope(
     base::PassKey<content::FedCmMetrics>,
     const GURL& provider_url) {
diff --git a/services/metrics/public/cpp/ukm_recorder.h b/services/metrics/public/cpp/ukm_recorder.h
index ed22471..29bf51c 100644
--- a/services/metrics/public/cpp/ukm_recorder.h
+++ b/services/metrics/public/cpp/ukm_recorder.h
@@ -34,10 +34,6 @@
 class RenderFrameHostImpl;
 }  // namespace content
 
-namespace web_app {
-class DesktopWebAppUkmRecorder;
-}
-
 namespace extensions {
 class ExtensionMessagePort;
 }
@@ -98,12 +94,6 @@
       base::PassKey<WebApkUkmRecorder>,
       const GURL& manifest_url);
 
-  // Gets new source ID for a desktop web app, using the start_url from the web
-  // app manifest. This method should only be called by DailyMetricsHelper.
-  static SourceId GetSourceIdForDesktopWebAppStartUrl(
-      base::PassKey<web_app::DesktopWebAppUkmRecorder>,
-      const GURL& start_url);
-
   // Gets new source Id for PAYMENT_APP_ID type and updates the source url to
   // the scope of the app. This method should only be called by
   // PaymentAppProviderUtil class when the payment app window is opened.
diff --git a/services/metrics/public/cpp/ukm_source.cc b/services/metrics/public/cpp/ukm_source.cc
index 40453823..035d235 100644
--- a/services/metrics/public/cpp/ukm_source.cc
+++ b/services/metrics/public/cpp/ukm_source.cc
@@ -52,7 +52,7 @@
       return SourceType::WEBAPK_ID;
     case SourceIdType::PAYMENT_APP_ID:
       return SourceType::PAYMENT_APP_ID;
-    case SourceIdType::DESKTOP_WEB_APP_ID:
+    case SourceIdType::DEPRECATED_DESKTOP_WEB_APP_ID:
       return SourceType::DESKTOP_WEB_APP_ID;
     case SourceIdType::WORKER_ID:
       return SourceType::WORKER_ID;
diff --git a/services/metrics/public/cpp/ukm_source_id.cc b/services/metrics/public/cpp/ukm_source_id.cc
index 811ff6f..ed7c07a 100644
--- a/services/metrics/public/cpp/ukm_source_id.cc
+++ b/services/metrics/public/cpp/ukm_source_id.cc
@@ -99,7 +99,7 @@
       return "WEBAPK_ID";
     case SourceIdObj::Type::PAYMENT_APP_ID:
       return "PAYMENT_APP_ID";
-    case SourceIdObj::Type::DESKTOP_WEB_APP_ID:
+    case SourceIdObj::Type::DEPRECATED_DESKTOP_WEB_APP_ID:
       return "DESKTOP_WEB_APP_ID";
     case SourceIdObj::Type::WORKER_ID:
       return "WORKER_ID";
diff --git a/services/metrics/public/cpp/ukm_source_id.h b/services/metrics/public/cpp/ukm_source_id.h
index 028c7fd..4af9acc 100644
--- a/services/metrics/public/cpp/ukm_source_id.h
+++ b/services/metrics/public/cpp/ukm_source_id.h
@@ -39,9 +39,9 @@
     // the max threshold.
     NAVIGATION_ID = 1,
     // Source ID used by AppLaunchEventLogger::Log and
-    // AppPlatformMetrics::GetSourceId. They will be kept in memory as long as
-    // the associated app is still running and the number of sources are within
-    // the max threshold.
+    // AppPlatformMetrics::GetSourceId and DesktopWebAppUkmRecorder. They will
+    // be kept in memory as long as the associated app is still running and the
+    // number of sources are within the max threshold.
     APP_ID = 2,
     // Source ID for background events that don't have an open tab but the
     // associated URL is still present in the browsing history. A new source of
@@ -57,13 +57,8 @@
     // type and associated events are expected to be recorded within the same
     // report interval; it will not be kept in memory between different reports.
     PAYMENT_APP_ID = 5,
-    // Source ID for desktop web apps, based on the start_url in the web app
-    // manifest. A new source of this type and associated events are expected to
-    // be recorded within the same report interval; it will not be kept in
-    // memory between different reports.
-    // TODO(crbug.com/1441376): Deprecate and migrate usages to AppKm so UKM can
-    // reuse code and enforce the same checks.
-    DESKTOP_WEB_APP_ID = 6,
+    // DEPRECATED. Use APP_ID instead.
+    DEPRECATED_DESKTOP_WEB_APP_ID = 6,
     // Source ID for web workers, namely SharedWorkers and ServiceWorkers. Web
     // workers may inherit a source ID from the spawner context (in the case of
     // dedicated workers), or may have their own source IDs (in the case of
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index 550b01b..2315df4 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -143,12 +143,16 @@
     "shared_dictionary/shared_dictionary_manager.h",
     "shared_dictionary/shared_dictionary_manager_in_memory.cc",
     "shared_dictionary/shared_dictionary_manager_in_memory.h",
+    "shared_dictionary/shared_dictionary_manager_on_disk.cc",
+    "shared_dictionary/shared_dictionary_manager_on_disk.h",
     "shared_dictionary/shared_dictionary_on_disk.cc",
     "shared_dictionary/shared_dictionary_on_disk.h",
     "shared_dictionary/shared_dictionary_storage.cc",
     "shared_dictionary/shared_dictionary_storage.h",
     "shared_dictionary/shared_dictionary_storage_in_memory.cc",
     "shared_dictionary/shared_dictionary_storage_in_memory.h",
+    "shared_dictionary/shared_dictionary_storage_on_disk.cc",
+    "shared_dictionary/shared_dictionary_storage_on_disk.h",
     "shared_dictionary/shared_dictionary_writer.h",
     "shared_dictionary/shared_dictionary_writer_in_memory.cc",
     "shared_dictionary/shared_dictionary_writer_in_memory.h",
@@ -420,6 +424,7 @@
     "session_cleanup_cookie_store_unittest.cc",
     "shared_dictionary/shared_dictionary_data_pipe_writer_unittest.cc",
     "shared_dictionary/shared_dictionary_disk_cache_unittest.cc",
+    "shared_dictionary/shared_dictionary_manager_on_disk_unittest.cc",
     "shared_dictionary/shared_dictionary_manager_unittest.cc",
     "shared_dictionary/shared_dictionary_on_disk_unittest.cc",
     "shared_dictionary/shared_dictionary_writer_in_memory_unittest.cc",
@@ -522,6 +527,7 @@
     "//services/network/trust_tokens:tests",
     "//services/service_manager/public/cpp",
     "//services/service_manager/public/cpp/test:test_support",
+    "//sql:test_support",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/services/network/public/cpp/corb/corb_api.cc b/services/network/public/cpp/corb/corb_api.cc
index ffa6d85..b240f964 100644
--- a/services/network/public/cpp/corb/corb_api.cc
+++ b/services/network/public/cpp/corb/corb_api.cc
@@ -52,9 +52,8 @@
   explicit ComparingAnalyzer(PerFactoryState& state)
       : corb_analyzer_(
             std::make_unique<CrossOriginReadBlocking::CorbResponseAnalyzer>()),
-        orb_analyzer_(std::make_unique<OpaqueResponseBlockingAnalyzer>(state)),
-        is_orb_enabled_(base::FeatureList::IsEnabled(
-            features::kOpaqueResponseBlockingV01_LAUNCHED)) {}
+        orb_analyzer_(std::make_unique<OpaqueResponseBlockingAnalyzer>(state)) {
+  }
 
   ~ComparingAnalyzer() override {
     Comparison comparison = Comparison::kInvalid;
@@ -134,20 +133,14 @@
       return Decision::kSniffMore;
     }
 
-    return is_orb_enabled_ ? orb_decision_ : corb_decision_;
+    return orb_decision_;
   }
 
-  const ResponseAnalyzer& GetEnabledAnalyzer() const {
-    if (is_orb_enabled_)
-      return *orb_analyzer_;
-    else
-      return *corb_analyzer_;
-  }
+  const ResponseAnalyzer& GetEnabledAnalyzer() const { return *orb_analyzer_; }
 
   const std::unique_ptr<CrossOriginReadBlocking::CorbResponseAnalyzer>
       corb_analyzer_;
   const std::unique_ptr<OpaqueResponseBlockingAnalyzer> orb_analyzer_;
-  const bool is_orb_enabled_ = false;
 
   Decision corb_decision_ = Decision::kSniffMore;
   Decision orb_decision_ = Decision::kSniffMore;
diff --git a/services/network/public/cpp/corb/corb_impl.cc b/services/network/public/cpp/corb/corb_impl.cc
index 5945acbb..b3b64d2 100644
--- a/services/network/public/cpp/corb/corb_impl.cc
+++ b/services/network/public/cpp/corb/corb_impl.cc
@@ -637,8 +637,7 @@
         mojom::RequestMode::kNoCors, request_url,
         cross_origin_request_initiator, response, canonical_mime_type_);
     corb_protection_logging_needs_sniffing_ =
-        (would_protect_based_on_headers == Decision::kSniffMore) &&
-        base::FeatureList::IsEnabled(features::kCORBProtectionSniffing);
+        (would_protect_based_on_headers == Decision::kSniffMore);
     hypothetical_sniffing_mode_ =
         corb_protection_logging_needs_sniffing_ &&
         should_block_based_on_headers_ != Decision::kSniffMore;
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index a4ad24c8..648dc6395 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -50,12 +50,6 @@
              "PauseBrowserInitiatedHeavyTrafficForP2P",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// When kCORBProtectionSniffing is enabled CORB sniffs additional same-origin
-// resources if they look sensitive.
-BASE_FEATURE(kCORBProtectionSniffing,
-             "CORBProtectionSniffing",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // When kProactivelyThrottleLowPriorityRequests is enabled,
 // resource scheduler proactively throttles low priority requests to avoid
 // network contention with high priority requests that may arrive soon.
@@ -118,27 +112,10 @@
              "MdnsResponderGeneratedNameListing",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Switches Cross-Origin Read Blocking (CORB) to use an early implementation of
-// Opaque Response Blocking (ORB, aka CORB++) behind the scenes.
-//
-// This is ORB v0.1 - it doesn't implement the full spec from
-// https://github.com/annevk/orb:
-// - No Javascript sniffing is done.  Instead the implementation uses all
-//   of CORB's confirmation sniffers (for HTML, XML and JSON).
-// - Blocking is still done by injecting an empty response rather than erroring
-//   out the network request
-// - Other differences and more details can be found in
-//   //services/network/public/cpp/corb/README.md
-//
-// Implementing ORB in Chromium is tracked in https://crbug.com/1178928
-BASE_FEATURE(kOpaqueResponseBlockingV01_LAUNCHED,
-             "OpaqueResponseBlockingV01",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables ORB blocked responses being treated as errors (according to the spec)
 // rather than the current, CORB-style handling of injecting an empty response.
 // This is ORB v0.2.
-// This should only be enabled when ORB v0.1 is, too.
+// Implementing ORB in Chromium is tracked in https://crbug.com/1178928
 BASE_FEATURE(kOpaqueResponseBlockingV02,
              "OpaqueResponseBlockingV02",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/services/network/public/cpp/features.gni b/services/network/public/cpp/features.gni
index 9204fdd..b806e84 100644
--- a/services/network/public/cpp/features.gni
+++ b/services/network/public/cpp/features.gni
@@ -6,7 +6,6 @@
 
 declare_args() {
   # Certificate transparency is not supported on iOS.
-  # TODO(mmenke): It's actually not supported on Android, either.
   is_ct_supported = !is_ios
 
   # Controls whether P2P is exposed by the network service.
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index 7acc973..c5b2d09 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -20,7 +20,6 @@
 BASE_DECLARE_FEATURE(kDelayRequestsOnMultiplexedConnections);
 COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kPauseBrowserInitiatedHeavyTrafficForP2P);
-COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kCORBProtectionSniffing);
 COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kProactivelyThrottleLowPriorityRequests);
 COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kCrossOriginOpenerPolicy);
@@ -33,8 +32,6 @@
 COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kMaskedDomainList);
 COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kMdnsResponderGeneratedNameListing);
-COMPONENT_EXPORT(NETWORK_CPP)
-BASE_DECLARE_FEATURE(kOpaqueResponseBlockingV01_LAUNCHED);
 COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kOpaqueResponseBlockingV02);
 
 COMPONENT_EXPORT(NETWORK_CPP)
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index 7c23274..fffdf6f 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -864,10 +864,6 @@
           cpp = "::net::CertVerifyResult"
         },
         {
-          mojom = "network.mojom.CTVerifyResult"
-          cpp = "::net::ct::CTVerifyResult"
-        },
-        {
           mojom = "network.mojom.HttpResponseHeaders"
           cpp = "::scoped_refptr<::net::HttpResponseHeaders>"
           nullable_is_same_type = true
diff --git a/services/network/shared_dictionary/DEPS b/services/network/shared_dictionary/DEPS
new file mode 100644
index 0000000..9228d0e
--- /dev/null
+++ b/services/network/shared_dictionary/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+  "shared_dictionary_manager_on_disk_unittest\.cc": [
+    "+sql/test/test_helpers.h",
+  ],
+}
diff --git a/services/network/shared_dictionary/shared_dictionary_constants.cc b/services/network/shared_dictionary/shared_dictionary_constants.cc
index 282104d..a7bec5a 100644
--- a/services/network/shared_dictionary/shared_dictionary_constants.cc
+++ b/services/network/shared_dictionary/shared_dictionary_constants.cc
@@ -4,6 +4,8 @@
 
 #include "services/network/shared_dictionary/shared_dictionary_constants.h"
 
+#include "base/functional/callback.h"
+
 namespace network::shared_dictionary {
 
 namespace {
@@ -24,8 +26,11 @@
   return g_dictionary_size_limit;
 }
 
-void SetDictionarySizeLimitForTesting(size_t dictionary_size_limit) {
+base::ScopedClosureRunner SetDictionarySizeLimitForTesting(  // IN-TEST
+    size_t dictionary_size_limit) {
   g_dictionary_size_limit = dictionary_size_limit;
+  return base::ScopedClosureRunner(
+      base::BindOnce([]() { g_dictionary_size_limit = kDictionarySizeLimit; }));
 }
 
 }  // namespace network::shared_dictionary
diff --git a/services/network/shared_dictionary/shared_dictionary_constants.h b/services/network/shared_dictionary/shared_dictionary_constants.h
index 9d28ce6..c22f892e 100644
--- a/services/network/shared_dictionary/shared_dictionary_constants.h
+++ b/services/network/shared_dictionary/shared_dictionary_constants.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/component_export.h"
+#include "base/functional/callback_helpers.h"
 #include "base/time/time.h"
 
 namespace network::shared_dictionary {
@@ -19,8 +20,11 @@
 // The size limit of a shared dictionary.
 size_t GetDictionarySizeLimit();
 
+// Changes the size limit of a shared dictionary, and returns a
+// ScopedClosureRunner which will reset the size limit in the destructor.
 COMPONENT_EXPORT(NETWORK_SERVICE)
-void SetDictionarySizeLimitForTesting(size_t dictionary_size_limit);
+base::ScopedClosureRunner SetDictionarySizeLimitForTesting(
+    size_t dictionary_size_limit);
 
 // The header name of "use-as-dictionary".
 COMPONENT_EXPORT(NETWORK_SERVICE)
diff --git a/services/network/shared_dictionary/shared_dictionary_disk_cache.cc b/services/network/shared_dictionary/shared_dictionary_disk_cache.cc
index 0fc4ff1f..ee560c0d 100644
--- a/services/network/shared_dictionary/shared_dictionary_disk_cache.cc
+++ b/services/network/shared_dictionary/shared_dictionary_disk_cache.cc
@@ -10,7 +10,7 @@
 namespace network {
 namespace {
 
-void RunTaksAndCallback(
+void RunTaskAndCallback(
     base::OnceCallback<int(net::CompletionOnceCallback)> task,
     net::CompletionOnceCallback callback) {
   auto split_callback = base::SplitOnceCallback(std::move(callback));
@@ -123,7 +123,7 @@
       // `SharedDictionaryDiskCache::DoomEntry()` will be called only when
       // `this` is available.
       pending_disk_cache_tasks_.push_back(
-          base::BindOnce(&RunTaksAndCallback,
+          base::BindOnce(&RunTaskAndCallback,
                          base::BindOnce(&SharedDictionaryDiskCache::DoomEntry,
                                         base::Unretained(this), key),
                          std::move(callback)));
@@ -147,7 +147,7 @@
       // `SharedDictionaryDiskCache::ClearAll()` will be called only when `this`
       // is available.
       pending_disk_cache_tasks_.push_back(
-          base::BindOnce(&RunTaksAndCallback,
+          base::BindOnce(&RunTaskAndCallback,
                          base::BindOnce(&SharedDictionaryDiskCache::ClearAll,
                                         base::Unretained(this)),
                          std::move(callback)));
diff --git a/services/network/shared_dictionary/shared_dictionary_manager.cc b/services/network/shared_dictionary/shared_dictionary_manager.cc
index b66cd37..ff172c55 100644
--- a/services/network/shared_dictionary/shared_dictionary_manager.cc
+++ b/services/network/shared_dictionary/shared_dictionary_manager.cc
@@ -5,6 +5,7 @@
 #include "services/network/shared_dictionary/shared_dictionary_manager.h"
 
 #include "services/network/shared_dictionary/shared_dictionary_manager_in_memory.h"
+#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
 #include "services/network/shared_dictionary/shared_dictionary_storage.h"
 
 namespace network {
@@ -15,6 +16,23 @@
   return std::make_unique<SharedDictionaryManagerInMemory>();
 }
 
+// static
+std::unique_ptr<SharedDictionaryManager> SharedDictionaryManager::CreateOnDisk(
+    const base::FilePath& database_path,
+    const base::FilePath& cache_directory_path,
+#if BUILDFLAG(IS_ANDROID)
+    base::android::ApplicationStatusListener* app_status_listener,
+#endif  // BUILDFLAG(IS_ANDROID)
+    scoped_refptr<disk_cache::BackendFileOperationsFactory>
+        file_operations_factory) {
+  return std::make_unique<SharedDictionaryManagerOnDisk>(
+      database_path, cache_directory_path,
+#if BUILDFLAG(IS_ANDROID)
+      app_status_listener,
+#endif  // BUILDFLAG(IS_ANDROID)
+      std::move(file_operations_factory));
+}
+
 SharedDictionaryManager::SharedDictionaryManager() = default;
 SharedDictionaryManager::~SharedDictionaryManager() = default;
 
@@ -26,6 +44,7 @@
     return it->second.get();
   }
   scoped_refptr<SharedDictionaryStorage> storage = CreateStorage(isolation_key);
+  CHECK(storage);
   storages_.emplace(isolation_key, storage.get());
   return storage;
 }
diff --git a/services/network/shared_dictionary/shared_dictionary_manager.h b/services/network/shared_dictionary/shared_dictionary_manager.h
index 0c9d588..3d39eef 100644
--- a/services/network/shared_dictionary/shared_dictionary_manager.h
+++ b/services/network/shared_dictionary/shared_dictionary_manager.h
@@ -12,6 +12,17 @@
 #include "base/memory/weak_ptr.h"
 #include "net/extras/shared_dictionary/shared_dictionary_storage_isolation_key.h"
 
+namespace base {
+namespace android {
+class ApplicationStatusListener;
+}  // namespace android
+class FilePath;
+}  //  namespace base
+
+namespace disk_cache {
+class BackendFileOperationsFactory;
+}  // namespace disk_cache
+
 namespace network {
 
 class SharedDictionaryStorage;
@@ -24,6 +35,17 @@
   // information in memory.
   static std::unique_ptr<SharedDictionaryManager> CreateInMemory();
 
+  // Returns a SharedDictionaryManager which keeps the dictionary information
+  // on disk.
+  static std::unique_ptr<SharedDictionaryManager> CreateOnDisk(
+      const base::FilePath& database_path,
+      const base::FilePath& cache_directory_path,
+#if BUILDFLAG(IS_ANDROID)
+      base::android::ApplicationStatusListener* app_status_listener,
+#endif  // BUILDFLAG(IS_ANDROID)
+      scoped_refptr<disk_cache::BackendFileOperationsFactory>
+          file_operations_factory);
+
   // TODO(crbug.com/1413922): Implement a manager which supports persistence
   // and use if for non-incognito mode. Also, if preventing incognito mode
   // detection isn't that important, and the maintenance cost of two storagee is
diff --git a/services/network/shared_dictionary/shared_dictionary_manager_on_disk.cc b/services/network/shared_dictionary/shared_dictionary_manager_on_disk.cc
new file mode 100644
index 0000000..da17312
--- /dev/null
+++ b/services/network/shared_dictionary/shared_dictionary_manager_on_disk.cc
@@ -0,0 +1,49 @@
+// 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 "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
+
+#include "base/functional/callback_helpers.h"
+#include "base/notreached.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h"
+
+namespace network {
+
+SharedDictionaryManagerOnDisk::SharedDictionaryManagerOnDisk(
+    const base::FilePath& database_path,
+    const base::FilePath& cache_directory_path,
+#if BUILDFLAG(IS_ANDROID)
+    base::android::ApplicationStatusListener* app_status_listener,
+#endif  // BUILDFLAG(IS_ANDROID)
+    scoped_refptr<disk_cache::BackendFileOperationsFactory>
+        file_operations_factory)
+    : metadata_store_(database_path,
+                      /*client_task_runner=*/
+                      base::SingleThreadTaskRunner::GetCurrentDefault(),
+                      /*background_task_runner=*/
+                      base::ThreadPool::CreateSequencedTaskRunner(
+                          {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
+                           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
+  disk_cache_.Initialize(cache_directory_path,
+#if BUILDFLAG(IS_ANDROID)
+                         app_status_listener,
+#endif  // BUILDFLAG(IS_ANDROID)
+                         std::move(file_operations_factory));
+}
+
+SharedDictionaryManagerOnDisk::~SharedDictionaryManagerOnDisk() = default;
+
+scoped_refptr<SharedDictionaryStorage>
+SharedDictionaryManagerOnDisk::CreateStorage(
+    const net::SharedDictionaryStorageIsolationKey& isolation_key) {
+  return base::MakeRefCounted<SharedDictionaryStorageOnDisk>(
+      weak_factory_.GetWeakPtr(), isolation_key,
+      base::ScopedClosureRunner(
+          base::BindOnce(&SharedDictionaryManager::OnStorageDeleted,
+                         GetWeakPtr(), isolation_key)));
+}
+
+}  // namespace network
diff --git a/services/network/shared_dictionary/shared_dictionary_manager_on_disk.h b/services/network/shared_dictionary/shared_dictionary_manager_on_disk.h
new file mode 100644
index 0000000..aec4d3a0
--- /dev/null
+++ b/services/network/shared_dictionary/shared_dictionary_manager_on_disk.h
@@ -0,0 +1,65 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_MANAGER_ON_DISK_H_
+#define SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_MANAGER_ON_DISK_H_
+
+#include "base/memory/weak_ptr.h"
+#include "build/build_config.h"
+#include "net/extras/sqlite/sqlite_persistent_shared_dictionary_store.h"
+#include "services/network/shared_dictionary/shared_dictionary_disk_cache.h"
+#include "services/network/shared_dictionary/shared_dictionary_manager.h"
+
+namespace base {
+namespace android {
+class ApplicationStatusListener;
+}  // namespace android
+class FilePath;
+}  //  namespace base
+
+namespace disk_cache {
+class BackendFileOperationsFactory;
+}  // namespace disk_cache
+
+namespace network {
+
+class SharedDictionaryStorage;
+
+// A SharedDictionaryManager which persists dictionary information on disk.
+class SharedDictionaryManagerOnDisk : public SharedDictionaryManager {
+ public:
+  SharedDictionaryManagerOnDisk(
+      const base::FilePath& database_path,
+      const base::FilePath& cache_directory_path,
+#if BUILDFLAG(IS_ANDROID)
+      base::android::ApplicationStatusListener* app_status_listener,
+#endif  // BUILDFLAG(IS_ANDROID)
+      scoped_refptr<disk_cache::BackendFileOperationsFactory>
+          file_operations_factory);
+
+  SharedDictionaryManagerOnDisk(const SharedDictionaryManagerOnDisk&) = delete;
+  SharedDictionaryManagerOnDisk& operator=(
+      const SharedDictionaryManagerOnDisk&) = delete;
+
+  ~SharedDictionaryManagerOnDisk() override;
+
+  // SharedDictionaryManager
+  scoped_refptr<SharedDictionaryStorage> CreateStorage(
+      const net::SharedDictionaryStorageIsolationKey& isolation_key) override;
+
+  SharedDictionaryDiskCache& disk_cache() { return disk_cache_; }
+  net::SQLitePersistentSharedDictionaryStore& metadata_store() {
+    return metadata_store_;
+  }
+
+ private:
+  SharedDictionaryDiskCache disk_cache_;
+  net::SQLitePersistentSharedDictionaryStore metadata_store_;
+
+  base::WeakPtrFactory<SharedDictionaryManagerOnDisk> weak_factory_{this};
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_MANAGER_ON_DISK_H_
diff --git a/services/network/shared_dictionary/shared_dictionary_manager_on_disk_unittest.cc b/services/network/shared_dictionary/shared_dictionary_manager_on_disk_unittest.cc
new file mode 100644
index 0000000..613e3855
--- /dev/null
+++ b/services/network/shared_dictionary/shared_dictionary_manager_on_disk_unittest.cc
@@ -0,0 +1,509 @@
+// 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 "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_file_util.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "crypto/secure_hash.h"
+#include "net/base/hash_value.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_isolation_key.h"
+#include "net/base/schemeful_site.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/extras/shared_dictionary/shared_dictionary_info.h"
+#include "net/http/http_response_headers.h"
+#include "services/network/shared_dictionary/shared_dictionary.h"
+#include "services/network/shared_dictionary/shared_dictionary_constants.h"
+#include "services/network/shared_dictionary/shared_dictionary_disk_cache.h"
+#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
+#include "services/network/shared_dictionary/shared_dictionary_storage.h"
+#include "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h"
+#include "services/network/shared_dictionary/shared_dictionary_writer.h"
+#include "sql/test/test_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace network {
+
+namespace {
+
+const GURL kUrl("https://origin.test/");
+const net::SchemefulSite kSite(kUrl);
+const std::string kTestData1 = "Hello world";
+const std::string kTestData2 = "Bonjour le monde";
+
+void WriteDictionary(SharedDictionaryStorage* storage,
+                     const GURL& dictionary_url,
+                     const std::string& match,
+                     const std::string& data) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(base::StrCat(
+          {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
+           ": match=\"/", match, "\"\n\n"}));
+  ASSERT_TRUE(headers);
+  scoped_refptr<SharedDictionaryWriter> writer =
+      storage->MaybeCreateWriter(dictionary_url, base::Time::Now(), *headers);
+  ASSERT_TRUE(writer);
+  writer->Append(data.c_str(), data.size());
+  writer->Finish();
+}
+
+bool DiskCacheEntryExists(SharedDictionaryManager* manager,
+                          const base::UnguessableToken& disk_cache_key_token) {
+  TestEntryResultCompletionCallback open_callback;
+  disk_cache::EntryResult open_result = open_callback.GetResult(
+      static_cast<SharedDictionaryManagerOnDisk*>(manager)
+          ->disk_cache()
+          .OpenOrCreateEntry(disk_cache_key_token.ToString(),
+                             /*create=*/false, open_callback.callback()));
+  return open_result.net_error() == net::OK;
+}
+
+}  // namespace
+
+class SharedDictionaryManagerOnDiskTest : public ::testing::Test {
+ public:
+  SharedDictionaryManagerOnDiskTest() = default;
+  ~SharedDictionaryManagerOnDiskTest() override = default;
+
+  SharedDictionaryManagerOnDiskTest(const SharedDictionaryManagerOnDiskTest&) =
+      delete;
+  SharedDictionaryManagerOnDiskTest& operator=(
+      const SharedDictionaryManagerOnDiskTest&) = delete;
+
+  void SetUp() override {
+    ASSERT_TRUE(tmp_directory_.CreateUniqueTempDir());
+    database_path_ = tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("db"));
+    cache_directory_path_ =
+        tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("cache"));
+  }
+  void TearDown() override { FlushCacheTasks(); }
+
+ protected:
+  std::unique_ptr<SharedDictionaryManager> CreateSharedDictionaryManager() {
+    return SharedDictionaryManager::CreateOnDisk(
+        database_path_, cache_directory_path_,
+#if BUILDFLAG(IS_ANDROID)
+        /*app_status_listener=*/nullptr,
+#endif  // BUILDFLAG(IS_ANDROID)
+        /*file_operations_factory=*/nullptr);
+  }
+  const std::map<url::SchemeHostPort,
+                 std::map<std::string, net::SharedDictionaryInfo>>&
+  GetOnDiskDictionaryMap(SharedDictionaryStorage* storage) {
+    return static_cast<SharedDictionaryStorageOnDisk*>(storage)
+        ->GetDictionaryMapForTesting();
+  }
+  void FlushCacheTasks() {
+    disk_cache::FlushCacheThreadForTesting();
+    task_environment_.RunUntilIdle();
+  }
+  void CorruptDiskCache() {
+    // Corrupt the fake index file for the populated simple cache.
+    const base::FilePath index_file_path =
+        cache_directory_path_.Append(FILE_PATH_LITERAL("index"));
+    ASSERT_TRUE(base::WriteFile(index_file_path, "corrupted"));
+    file_permissions_restorer_ = std::make_unique<base::FilePermissionRestorer>(
+        tmp_directory_.GetPath());
+    // Mark the parent directory unwritable, so that we can't restore the dist
+    ASSERT_TRUE(base::MakeFileUnwritable(tmp_directory_.GetPath()));
+  }
+
+  void CorruptDatabase() {
+    CHECK(sql::test::CorruptSizeInHeader(database_path_));
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  base::ScopedTempDir tmp_directory_;
+  base::FilePath database_path_;
+  base::FilePath cache_directory_path_;
+  // `file_permissions_restorer_` must be below `tmp_directory_` to restore the
+  // file permission correctly.
+  std::unique_ptr<base::FilePermissionRestorer> file_permissions_restorer_;
+};
+
+TEST_F(SharedDictionaryManagerOnDiskTest, ReusingRefCountedSharedDictionary) {
+  std::unique_ptr<SharedDictionaryManager> manager =
+      CreateSharedDictionaryManager();
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+  scoped_refptr<SharedDictionaryStorage> storage =
+      manager->GetStorage(isolation_key);
+  ASSERT_TRUE(storage);
+
+  WriteDictionary(storage.get(), GURL("https://origin.test/dict"), "testfile*",
+                  kTestData1);
+
+  FlushCacheTasks();
+
+  // Check the returned dictionary from GetDictionary().
+  std::unique_ptr<SharedDictionary> dict1 =
+      storage->GetDictionary(GURL("https://origin.test/testfile?1"));
+  ASSERT_TRUE(dict1);
+  {
+    base::RunLoop run_loop;
+    EXPECT_EQ(net::ERR_IO_PENDING,
+              dict1->ReadAll(base::BindLambdaForTesting([&](int rv) {
+                EXPECT_EQ(net::OK, rv);
+                run_loop.Quit();
+              })));
+    run_loop.Run();
+  }
+  std::unique_ptr<SharedDictionary> dict2 =
+      storage->GetDictionary(GURL("https://origin.test/testfile?2"));
+  ASSERT_TRUE(dict2);
+  // `dict2` shares the same RefCountedSharedDictionary with `dict1`. So
+  // ReadAll() must synchronously return OK.
+  EXPECT_EQ(net::OK, dict2->ReadAll(base::BindLambdaForTesting(
+                         [&](int rv) { NOTREACHED(); })));
+  // `dict2` shares the same IOBuffer with `dict1`.
+  EXPECT_EQ(dict1->data(), dict2->data());
+  EXPECT_EQ(dict1->size(), dict2->size());
+  EXPECT_EQ(dict1->hash(), dict2->hash());
+  EXPECT_EQ(kTestData1,
+            std::string(reinterpret_cast<const char*>(dict1->data()->data()),
+                        dict1->size()));
+}
+
+TEST_F(SharedDictionaryManagerOnDiskTest,
+       MaybeCreateWriterAfterManagerDeleted) {
+  std::unique_ptr<SharedDictionaryManager> manager =
+      CreateSharedDictionaryManager();
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+  scoped_refptr<SharedDictionaryStorage> storage =
+      manager->GetStorage(isolation_key);
+  ASSERT_TRUE(storage);
+
+  manager.reset();
+
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(base::StrCat(
+          {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
+           ": match=\"/testfile*\"\n\n"}));
+  ASSERT_TRUE(headers);
+
+  // MaybeCreateWriter() must return nullptr, after `manager` was deleted.
+  scoped_refptr<SharedDictionaryWriter> writer = storage->MaybeCreateWriter(
+      GURL("https://origin.test/dict"), base::Time::Now(), *headers);
+  EXPECT_FALSE(writer);
+}
+
+TEST_F(SharedDictionaryManagerOnDiskTest, GetDictionaryAfterManagerDeleted) {
+  std::unique_ptr<SharedDictionaryManager> manager =
+      CreateSharedDictionaryManager();
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+  scoped_refptr<SharedDictionaryStorage> storage =
+      manager->GetStorage(isolation_key);
+  ASSERT_TRUE(storage);
+
+  manager.reset();
+
+  // GetDictionary() must return nullptr, after `manager` was deleted.
+  std::unique_ptr<SharedDictionary> dict =
+      storage->GetDictionary(GURL("https://origin.test/testfile?1"));
+  EXPECT_FALSE(dict);
+}
+
+TEST_F(SharedDictionaryManagerOnDiskTest,
+       DictionaryWrittenInDiskCacheAfterManagerDeleted) {
+  std::unique_ptr<SharedDictionaryManager> manager =
+      CreateSharedDictionaryManager();
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+  scoped_refptr<SharedDictionaryStorage> storage =
+      manager->GetStorage(isolation_key);
+  ASSERT_TRUE(storage);
+  // Write the test data to the dictionary.
+  WriteDictionary(storage.get(), GURL("https://origin.test/dict"), "testfile*",
+                  kTestData1);
+  // Test that deleting `manager` while writing the dictionary doesn't cause
+  // crash.
+  manager.reset();
+  FlushCacheTasks();
+}
+
+TEST_F(SharedDictionaryManagerOnDiskTest, OverridingDictionary) {
+  std::unique_ptr<SharedDictionaryManager> manager =
+      CreateSharedDictionaryManager();
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+  scoped_refptr<SharedDictionaryStorage> storage =
+      manager->GetStorage(isolation_key);
+  ASSERT_TRUE(storage);
+
+  // Write the test data to the dictionary.
+  WriteDictionary(storage.get(), GURL("https://origin.test/dict1"), "testfile*",
+                  kTestData1);
+  FlushCacheTasks();
+
+  base::UnguessableToken disk_cache_key_token1;
+  {
+    const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+    ASSERT_EQ(1u, dictionary_map.size());
+    ASSERT_EQ(1u, dictionary_map.begin()->second.size());
+    disk_cache_key_token1 =
+        dictionary_map.begin()->second.begin()->second.disk_cache_key_token();
+  }
+
+  // Check the returned dictionary from GetDictionary().
+  std::unique_ptr<SharedDictionary> dict1 =
+      storage->GetDictionary(GURL("https://origin.test/testfile"));
+  ASSERT_TRUE(dict1);
+
+  // The disk cache entry must exist.
+  EXPECT_TRUE(DiskCacheEntryExists(manager.get(), disk_cache_key_token1));
+
+  // Write different test data to the dictionary.
+  WriteDictionary(storage.get(), GURL("https://origin.test/dict2"), "testfile*",
+                  kTestData2);
+
+  FlushCacheTasks();
+
+  base::UnguessableToken disk_cache_key_token2;
+  {
+    const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+    ASSERT_EQ(1u, dictionary_map.size());
+    ASSERT_EQ(1u, dictionary_map.begin()->second.size());
+    disk_cache_key_token2 =
+        dictionary_map.begin()->second.begin()->second.disk_cache_key_token();
+  }
+
+  EXPECT_NE(disk_cache_key_token1, disk_cache_key_token2);
+
+  // The disk cache entry should have been doomed.
+  EXPECT_FALSE(DiskCacheEntryExists(manager.get(), disk_cache_key_token1));
+
+  std::unique_ptr<SharedDictionary> dict2 =
+      storage->GetDictionary(GURL("https://origin.test/testfile"));
+  ASSERT_TRUE(dict2);
+
+  // We can read the new dictionary from `dict2`.
+  net::TestCompletionCallback read_callback2;
+  EXPECT_EQ(net::OK, read_callback2.GetResult(
+                         dict2->ReadAll(read_callback2.callback())));
+  EXPECT_EQ(kTestData2,
+            std::string(reinterpret_cast<const char*>(dict2->data()->data()),
+                        dict2->size()));
+
+  // We can still read the old dictionary from `dict1`.
+  net::TestCompletionCallback read_callback1;
+  EXPECT_EQ(net::OK, read_callback1.GetResult(
+                         dict1->ReadAll(read_callback1.callback())));
+  EXPECT_EQ(kTestData1,
+            std::string(reinterpret_cast<const char*>(dict1->data()->data()),
+                        dict1->size()));
+}
+
+TEST_F(SharedDictionaryManagerOnDiskTest, MultipleDictionaries) {
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+
+  {
+    std::unique_ptr<SharedDictionaryManager> manager =
+        CreateSharedDictionaryManager();
+    scoped_refptr<SharedDictionaryStorage> storage =
+        manager->GetStorage(isolation_key);
+    ASSERT_TRUE(storage);
+
+    // Write the test data to the dictionary.
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict1"),
+                    "testfile1*", kTestData1);
+
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict2"),
+                    "testfile2*", kTestData2);
+
+    FlushCacheTasks();
+
+    std::unique_ptr<SharedDictionary> dict1 =
+        storage->GetDictionary(GURL("https://origin.test/testfile1"));
+    ASSERT_TRUE(dict1);
+
+    std::unique_ptr<SharedDictionary> dict2 =
+        storage->GetDictionary(GURL("https://origin.test/testfile2"));
+    ASSERT_TRUE(dict2);
+
+    net::TestCompletionCallback read_callback1;
+    EXPECT_EQ(net::OK, read_callback1.GetResult(
+                           dict1->ReadAll(read_callback1.callback())));
+    EXPECT_EQ(kTestData1,
+              std::string(reinterpret_cast<const char*>(dict1->data()->data()),
+                          dict1->size()));
+
+    net::TestCompletionCallback read_callback2;
+    EXPECT_EQ(net::OK, read_callback2.GetResult(
+                           dict2->ReadAll(read_callback2.callback())));
+    EXPECT_EQ(kTestData2,
+              std::string(reinterpret_cast<const char*>(dict2->data()->data()),
+                          dict2->size()));
+    // Releasing `dict1`, `dict2`, `storage` and `manager`.
+  }
+
+  // The dictionaries must be available after recreating `manager`.
+  std::unique_ptr<SharedDictionaryManager> manager =
+      CreateSharedDictionaryManager();
+  scoped_refptr<SharedDictionaryStorage> storage =
+      manager->GetStorage(isolation_key);
+  ASSERT_TRUE(storage);
+
+  FlushCacheTasks();
+
+  const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+  ASSERT_EQ(1u, dictionary_map.size());
+  ASSERT_EQ(2u, dictionary_map.begin()->second.size());
+
+  std::unique_ptr<SharedDictionary> dict1 =
+      storage->GetDictionary(GURL("https://origin.test/testfile1"));
+  ASSERT_TRUE(dict1);
+
+  std::unique_ptr<SharedDictionary> dict2 =
+      storage->GetDictionary(GURL("https://origin.test/testfile2"));
+  ASSERT_TRUE(dict2);
+
+  net::TestCompletionCallback read_callback1;
+  EXPECT_EQ(net::OK, read_callback1.GetResult(
+                         dict1->ReadAll(read_callback1.callback())));
+  EXPECT_EQ(kTestData1,
+            std::string(reinterpret_cast<const char*>(dict1->data()->data()),
+                        dict1->size()));
+
+  net::TestCompletionCallback read_callback2;
+  EXPECT_EQ(net::OK, read_callback2.GetResult(
+                         dict2->ReadAll(read_callback2.callback())));
+  EXPECT_EQ(kTestData2,
+            std::string(reinterpret_cast<const char*>(dict2->data()->data()),
+                        dict2->size()));
+}
+
+#if !BUILDFLAG(IS_FUCHSIA)
+// Test that corruptted disk cache doesn't cause crash.
+// CorruptDiskCache() doesn't work on Fuchsia. So disabling the following tests
+// on Fuchsia.
+TEST_F(SharedDictionaryManagerOnDiskTest, CorruptedDiskCache) {
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+
+  {
+    std::unique_ptr<SharedDictionaryManager> manager =
+        CreateSharedDictionaryManager();
+    scoped_refptr<SharedDictionaryStorage> storage =
+        manager->GetStorage(isolation_key);
+    ASSERT_TRUE(storage);
+    // Write the test data to the dictionary.
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict1"),
+                    "testfile1*", kTestData1);
+    FlushCacheTasks();
+  }
+  CorruptDiskCache();
+  {
+    std::unique_ptr<SharedDictionaryManager> manager =
+        CreateSharedDictionaryManager();
+    scoped_refptr<SharedDictionaryStorage> storage =
+        manager->GetStorage(isolation_key);
+    ASSERT_TRUE(storage);
+    FlushCacheTasks();
+    {
+      const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+      ASSERT_EQ(1u, dictionary_map.size());
+      ASSERT_EQ(1u, dictionary_map.begin()->second.size());
+    }
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict2"),
+                    "testfile2*", kTestData2);
+    FlushCacheTasks();
+    // Currently, if the disk cache is corrupted, it just prevents adding new
+    // dictionaries.
+    // TODO(crbug.com/1413922): Implement a garbage collection logic to remove
+    // the entry in the database when its disk cache entry is unavailable.
+    {
+      const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+      ASSERT_EQ(1u, dictionary_map.size());
+      ASSERT_EQ(1u, dictionary_map.begin()->second.size());
+    }
+  }
+}
+#endif  // !BUILDFLAG(IS_FUCHSIA)
+
+TEST_F(SharedDictionaryManagerOnDiskTest, CorruptedDatabase) {
+  net::SharedDictionaryStorageIsolationKey isolation_key(
+      url::Origin::Create(kUrl), kSite);
+
+  {
+    std::unique_ptr<SharedDictionaryManager> manager =
+        CreateSharedDictionaryManager();
+    scoped_refptr<SharedDictionaryStorage> storage =
+        manager->GetStorage(isolation_key);
+    ASSERT_TRUE(storage);
+    // Write the test data to the dictionary.
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict"),
+                    "testfile*", kTestData1);
+    FlushCacheTasks();
+    {
+      const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+      ASSERT_EQ(1u, dictionary_map.size());
+      ASSERT_EQ(1u, dictionary_map.begin()->second.size());
+    }
+  }
+  CorruptDatabase();
+  {
+    std::unique_ptr<SharedDictionaryManager> manager =
+        CreateSharedDictionaryManager();
+    scoped_refptr<SharedDictionaryStorage> storage =
+        manager->GetStorage(isolation_key);
+    ASSERT_TRUE(storage);
+    FlushCacheTasks();
+    EXPECT_TRUE(GetOnDiskDictionaryMap(storage.get()).empty());
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict"),
+                    "testfile*", kTestData1);
+    FlushCacheTasks();
+    // Can't add a new entry right after the databace corruption.
+    EXPECT_TRUE(GetOnDiskDictionaryMap(storage.get()).empty());
+  }
+  // Test that database corruption can be recovered after reboot.
+  {
+    std::unique_ptr<SharedDictionaryManager> manager =
+        CreateSharedDictionaryManager();
+    scoped_refptr<SharedDictionaryStorage> storage =
+        manager->GetStorage(isolation_key);
+    ASSERT_TRUE(storage);
+    FlushCacheTasks();
+    EXPECT_TRUE(GetOnDiskDictionaryMap(storage.get()).empty());
+    WriteDictionary(storage.get(), GURL("https://origin.test/dict"),
+                    "testfile*", kTestData1);
+    FlushCacheTasks();
+    EXPECT_FALSE(GetOnDiskDictionaryMap(storage.get()).empty());
+
+    std::unique_ptr<SharedDictionary> dict =
+        storage->GetDictionary(GURL("https://origin.test/testfile"));
+    ASSERT_TRUE(dict);
+
+    // We can read the new dictionary.
+    net::TestCompletionCallback read_callback;
+    EXPECT_EQ(net::OK,
+              read_callback.GetResult(dict->ReadAll(read_callback.callback())));
+    EXPECT_EQ(kTestData1,
+              std::string(reinterpret_cast<const char*>(dict->data()->data()),
+                          dict->size()));
+    // Currently the disk cache entries that were added before the database
+    // corruption will not be removed.
+    // TODO(crbug.com/1413922): Implement a garbage collection logic to remove
+    // the entry in the disk cache when its database entry is unavailable.
+  }
+}
+
+}  // namespace network
diff --git a/services/network/shared_dictionary/shared_dictionary_manager_unittest.cc b/services/network/shared_dictionary/shared_dictionary_manager_unittest.cc
index 6f6906a..4f92db79 100644
--- a/services/network/shared_dictionary/shared_dictionary_manager_unittest.cc
+++ b/services/network/shared_dictionary/shared_dictionary_manager_unittest.cc
@@ -4,9 +4,15 @@
 
 #include "services/network/shared_dictionary/shared_dictionary_manager.h"
 
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
 #include "base/functional/callback.h"
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "crypto/secure_hash.h"
 #include "net/base/hash_value.h"
@@ -14,11 +20,17 @@
 #include "net/base/net_errors.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/schemeful_site.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/extras/shared_dictionary/shared_dictionary_info.h"
 #include "net/http/http_response_headers.h"
 #include "services/network/shared_dictionary/shared_dictionary.h"
 #include "services/network/shared_dictionary/shared_dictionary_constants.h"
+#include "services/network/shared_dictionary/shared_dictionary_disk_cache.h"
+#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
 #include "services/network/shared_dictionary/shared_dictionary_storage.h"
 #include "services/network/shared_dictionary/shared_dictionary_storage_in_memory.h"
+#include "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h"
 #include "services/network/shared_dictionary/shared_dictionary_writer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -27,13 +39,69 @@
 namespace network {
 
 namespace {
+
+enum class TestManagerType {
+  kInMemory,
+  kOnDisk,
+};
+
 const GURL kUrl1("https://origin1.test/");
 const GURL kUrl2("https://origin2.test/");
 const net::SchemefulSite kSite1(kUrl1);
 const net::SchemefulSite kSite2(kUrl2);
+
+void CheckDiskCacheEntryDataEquals(
+    SharedDictionaryDiskCache& disk_cache,
+    const base::UnguessableToken& disk_cache_key_token,
+    const std::string& expected_data) {
+  TestEntryResultCompletionCallback open_callback;
+  disk_cache::EntryResult open_result = open_callback.GetResult(
+      disk_cache.OpenOrCreateEntry(disk_cache_key_token.ToString(),
+                                   /*create=*/false, open_callback.callback()));
+  EXPECT_EQ(net::OK, open_result.net_error());
+  disk_cache::ScopedEntryPtr entry;
+  entry.reset(open_result.ReleaseEntry());
+  ASSERT_TRUE(entry);
+
+  EXPECT_EQ(base::checked_cast<int32_t>(expected_data.size()),
+            entry->GetDataSize(/*index=*/1));
+
+  scoped_refptr<net::IOBufferWithSize> read_buffer =
+      base::MakeRefCounted<net::IOBufferWithSize>(expected_data.size());
+  net::TestCompletionCallback read_callback;
+  EXPECT_EQ(read_buffer->size(),
+            read_callback.GetResult(entry->ReadData(
+                /*index=*/1, /*offset=*/0, read_buffer.get(),
+                expected_data.size(), read_callback.callback())));
+  EXPECT_EQ(expected_data,
+            std::string(reinterpret_cast<const char*>(read_buffer->data()),
+                        read_buffer->size()));
+}
+
+void WriteDictionary(SharedDictionaryStorage* storage,
+                     const GURL& dictionary_url,
+                     const std::string& match,
+                     const std::vector<std::string>& data_list,
+                     base::Time now_time = base::Time::Now()) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(base::StrCat(
+          {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
+           ": match=\"/", match, "\"\n\n"}));
+  ASSERT_TRUE(headers);
+  scoped_refptr<SharedDictionaryWriter> writer =
+      storage->MaybeCreateWriter(dictionary_url, now_time, *headers);
+  ASSERT_TRUE(writer);
+  for (const std::string& data : data_list) {
+    writer->Append(data.c_str(), data.size());
+  }
+  writer->Finish();
+}
+
 }  // namespace
 
-class SharedDictionaryManagerTest : public ::testing::Test {
+class SharedDictionaryManagerTest
+    : public ::testing::Test,
+      public testing::WithParamInterface<TestManagerType> {
  public:
   SharedDictionaryManagerTest() = default;
   ~SharedDictionaryManagerTest() override = default;
@@ -42,7 +110,34 @@
   SharedDictionaryManagerTest& operator=(const SharedDictionaryManagerTest&) =
       delete;
 
+  void SetUp() override {
+    if (GetParam() == TestManagerType::kOnDisk) {
+      ASSERT_TRUE(tmp_directory_.CreateUniqueTempDir());
+      database_path_ = tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("db"));
+      cache_directory_path_ =
+          tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("cache"));
+    }
+  }
+  void TearDown() override {
+    if (GetParam() == TestManagerType::kOnDisk) {
+      FlushCacheTasks();
+    }
+  }
+
  protected:
+  std::unique_ptr<SharedDictionaryManager> CreateSharedDictionaryManager() {
+    switch (GetParam()) {
+      case TestManagerType::kInMemory:
+        return SharedDictionaryManager::CreateInMemory();
+      case TestManagerType::kOnDisk:
+        return SharedDictionaryManager::CreateOnDisk(
+            database_path_, cache_directory_path_,
+#if BUILDFLAG(IS_ANDROID)
+            /*app_status_listener=*/nullptr,
+#endif  // BUILDFLAG(IS_ANDROID)
+            /*file_operations_factory=*/nullptr);
+    }
+  }
   const std::map<
       url::SchemeHostPort,
       std::map<std::string, SharedDictionaryStorageInMemory::DictionaryInfo>>&
@@ -50,11 +145,40 @@
     return static_cast<SharedDictionaryStorageInMemory*>(storage)
         ->GetDictionaryMapForTesting();
   }
+  const std::map<url::SchemeHostPort,
+                 std::map<std::string, net::SharedDictionaryInfo>>&
+  GetOnDiskDictionaryMap(SharedDictionaryStorage* storage) {
+    return static_cast<SharedDictionaryStorageOnDisk*>(storage)
+        ->GetDictionaryMapForTesting();
+  }
+  void FlushCacheTasks() {
+    disk_cache::FlushCacheThreadForTesting();
+    task_environment_.RunUntilIdle();
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  base::ScopedTempDir tmp_directory_;
+  base::FilePath database_path_;
+  base::FilePath cache_directory_path_;
 };
 
-TEST_F(SharedDictionaryManagerTest, SameStorageForSameIsolationKey) {
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SharedDictionaryManagerTest,
+    testing::ValuesIn({TestManagerType::kInMemory, TestManagerType::kOnDisk}),
+    [](const testing::TestParamInfo<TestManagerType>& info) {
+      switch (info.param) {
+        case TestManagerType::kInMemory:
+          return "InMemory";
+        case TestManagerType::kOnDisk:
+          return "OnDisk";
+      }
+    });
+
+TEST_P(SharedDictionaryManagerTest, SameStorageForSameIsolationKey) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
 
   net::SharedDictionaryStorageIsolationKey isolation_key1(
       url::Origin::Create(kUrl1), kSite1);
@@ -73,9 +197,9 @@
   EXPECT_EQ(storage1.get(), storage2.get());
 }
 
-TEST_F(SharedDictionaryManagerTest, DifferentStorageForDifferentIsolationKey) {
+TEST_P(SharedDictionaryManagerTest, DifferentStorageForDifferentIsolationKey) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
 
   net::SharedDictionaryStorageIsolationKey isolation_key1(
       url::Origin::Create(kUrl1), kSite1);
@@ -93,9 +217,9 @@
   EXPECT_NE(storage1.get(), storage2.get());
 }
 
-TEST_F(SharedDictionaryManagerTest, NoWriterForNoUseAsDictionaryHeader) {
+TEST_P(SharedDictionaryManagerTest, NoWriterForNoUseAsDictionaryHeader) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
 
   net::SharedDictionaryStorageIsolationKey isolation_key(
       url::Origin::Create(kUrl1), kSite1);
@@ -112,9 +236,9 @@
   EXPECT_FALSE(writer);
 }
 
-TEST_F(SharedDictionaryManagerTest, WriterForUseAsDictionaryHeader) {
+TEST_P(SharedDictionaryManagerTest, WriterForUseAsDictionaryHeader) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
 
   net::SharedDictionaryStorageIsolationKey isolation_key(
       url::Origin::Create(kUrl1), kSite1);
@@ -182,27 +306,19 @@
   }
 }
 
-TEST_F(SharedDictionaryManagerTest, WriteAndGetDictionary) {
+TEST_P(SharedDictionaryManagerTest, WriteAndGetDictionary) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
   net::SharedDictionaryStorageIsolationKey isolation_key(
       url::Origin::Create(kUrl1), kSite1);
   scoped_refptr<SharedDictionaryStorage> storage =
       manager->GetStorage(isolation_key);
   ASSERT_TRUE(storage);
-  scoped_refptr<net::HttpResponseHeaders> headers =
-      net::HttpResponseHeaders::TryToCreate(base::StrCat(
-          {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
-           ": match=\"/testfile*\"\n\n"}));
-  ASSERT_TRUE(headers);
-
-  // Write the test data to the dictionary.
-  scoped_refptr<SharedDictionaryWriter> writer = storage->MaybeCreateWriter(
-      GURL("https://origin1.test/dict"), base::Time::Now(), *headers);
-  ASSERT_TRUE(writer);
-  const std::string data = "hello world";
-  writer->Append(data.c_str(), data.size());
-  writer->Finish();
+  WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*",
+                  {"hello world"});
+  if (GetParam() == TestManagerType::kOnDisk) {
+    FlushCacheTasks();
+  }
 
   // Check the returned dictionary from GetDictionary().
   EXPECT_TRUE(storage->GetDictionary(GURL("https://origin1.test/testfile")));
@@ -212,29 +328,20 @@
   EXPECT_FALSE(storage->GetDictionary(GURL("https://origin1.test/test")));
 }
 
-TEST_F(SharedDictionaryManagerTest, WriteAndReadDictionary) {
+TEST_P(SharedDictionaryManagerTest, WriteAndReadDictionary) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
   net::SharedDictionaryStorageIsolationKey isolation_key(
       url::Origin::Create(kUrl1), kSite1);
   scoped_refptr<SharedDictionaryStorage> storage =
       manager->GetStorage(isolation_key);
-  scoped_refptr<net::HttpResponseHeaders> headers =
-      net::HttpResponseHeaders::TryToCreate(base::StrCat(
-          {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
-           ": match=\"/testfile*\"\n\n"}));
-  ASSERT_TRUE(headers);
   base::Time now_time = base::Time::Now();
 
-  // Write the test data to the dictionary.
-  scoped_refptr<SharedDictionaryWriter> writer = storage->MaybeCreateWriter(
-      GURL("https://origin1.test/dict"), now_time, *headers);
-  ASSERT_TRUE(writer);
   const std::string data1 = "hello ";
   const std::string data2 = "world";
-  writer->Append(data1.c_str(), data1.size());
-  writer->Append(data2.c_str(), data2.size());
-  writer->Finish();
+  // Write the test data to the dictionary.
+  WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*",
+                  {data1, data2}, now_time);
 
   // Calculate the hash.
   std::unique_ptr<crypto::SecureHash> secure_hash =
@@ -244,6 +351,10 @@
   net::SHA256HashValue sha256;
   secure_hash->Finish(sha256.data, sizeof(sha256.data));
 
+  if (GetParam() == TestManagerType::kOnDisk) {
+    FlushCacheTasks();
+  }
+
   // Check the returned dictionary from GetDictionary().
   std::unique_ptr<SharedDictionary> dict =
       storage->GetDictionary(GURL("https://origin1.test/testfile?hello"));
@@ -252,50 +363,87 @@
   EXPECT_EQ(sha256, dict->hash());
 
   // Read and check the dictionary binary.
-  EXPECT_EQ(net::OK,
-            dict->ReadAll(base::BindOnce([](int rv) { NOTREACHED(); })));
+  switch (GetParam()) {
+    case TestManagerType::kInMemory: {
+      EXPECT_EQ(net::OK,
+                dict->ReadAll(base::BindOnce([](int rv) { NOTREACHED(); })));
+      break;
+    }
+    case TestManagerType::kOnDisk: {
+      base::RunLoop run_loop;
+      EXPECT_EQ(net::ERR_IO_PENDING,
+                dict->ReadAll(base::BindLambdaForTesting([&](int rv) {
+                  EXPECT_EQ(net::OK, rv);
+                  run_loop.Quit();
+                })));
+      run_loop.Run();
+      break;
+    }
+  }
+
   ASSERT_TRUE(dict->data());
   EXPECT_EQ(data1 + data2, std::string(dict->data()->data(), dict->size()));
 
-  // Check the internal state of SharedDictionaryStorageInMemory.
-  const auto& dictionary_map = GetInMemoryDictionaryMap(storage.get());
-  EXPECT_EQ(1u, dictionary_map.size());
-  EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")),
-            dictionary_map.begin()->first);
+  switch (GetParam()) {
+    case TestManagerType::kInMemory: {
+      // Check the internal state of SharedDictionaryStorageInMemory.
+      const auto& dictionary_map = GetInMemoryDictionaryMap(storage.get());
+      EXPECT_EQ(1u, dictionary_map.size());
+      EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")),
+                dictionary_map.begin()->first);
 
-  EXPECT_EQ(1u, dictionary_map.begin()->second.size());
-  EXPECT_EQ("/testfile*", dictionary_map.begin()->second.begin()->first);
-  const auto& dictionary_info = dictionary_map.begin()->second.begin()->second;
-  EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url());
-  EXPECT_EQ(now_time, dictionary_info.response_time());
-  EXPECT_EQ(shared_dictionary::kDefaultExpiration,
-            dictionary_info.expiration());
-  EXPECT_EQ("/testfile*", dictionary_info.match());
-  EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size());
-  EXPECT_EQ(data1 + data2, std::string(dictionary_info.data()->data(),
-                                       dictionary_info.size()));
-  EXPECT_EQ(sha256, dictionary_info.hash());
+      EXPECT_EQ(1u, dictionary_map.begin()->second.size());
+      EXPECT_EQ("/testfile*", dictionary_map.begin()->second.begin()->first);
+      const auto& dictionary_info =
+          dictionary_map.begin()->second.begin()->second;
+      EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url());
+      EXPECT_EQ(now_time, dictionary_info.response_time());
+      EXPECT_EQ(shared_dictionary::kDefaultExpiration,
+                dictionary_info.expiration());
+      EXPECT_EQ("/testfile*", dictionary_info.match());
+      EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size());
+      EXPECT_EQ(data1 + data2, std::string(dictionary_info.data()->data(),
+                                           dictionary_info.size()));
+      EXPECT_EQ(sha256, dictionary_info.hash());
+      break;
+    }
+    case TestManagerType::kOnDisk: {
+      // Check the internal state of SharedDictionaryStorageOnDisk.
+      const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
+      EXPECT_EQ(1u, dictionary_map.size());
+      EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")),
+                dictionary_map.begin()->first);
+
+      EXPECT_EQ(1u, dictionary_map.begin()->second.size());
+      EXPECT_EQ("/testfile*", dictionary_map.begin()->second.begin()->first);
+      const auto& dictionary_info =
+          dictionary_map.begin()->second.begin()->second;
+      EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url());
+      EXPECT_EQ(now_time, dictionary_info.response_time());
+      EXPECT_EQ(shared_dictionary::kDefaultExpiration,
+                dictionary_info.expiration());
+      EXPECT_EQ("/testfile*", dictionary_info.match());
+      EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size());
+      CheckDiskCacheEntryDataEquals(
+          static_cast<SharedDictionaryManagerOnDisk*>(manager.get())
+              ->disk_cache(),
+          dictionary_info.disk_cache_key_token(), data1 + data2);
+      EXPECT_EQ(sha256, dictionary_info.hash());
+      break;
+    }
+  }
 }
 
-TEST_F(SharedDictionaryManagerTest, ZeroSizeDictionaryShouldNotBeStored) {
+TEST_P(SharedDictionaryManagerTest, ZeroSizeDictionaryShouldNotBeStored) {
   std::unique_ptr<SharedDictionaryManager> manager =
-      SharedDictionaryManager::CreateInMemory();
+      CreateSharedDictionaryManager();
   net::SharedDictionaryStorageIsolationKey isolation_key(
       url::Origin::Create(kUrl1), kSite1);
   scoped_refptr<SharedDictionaryStorage> storage =
       manager->GetStorage(isolation_key);
-  scoped_refptr<net::HttpResponseHeaders> headers =
-      net::HttpResponseHeaders::TryToCreate(base::StrCat(
-          {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
-           ": match=\"/testfile*\"\n\n"}));
-  ASSERT_TRUE(headers);
-  base::Time now_time = base::Time::Now();
-
   // Write the zero size data to the dictionary.
-  scoped_refptr<SharedDictionaryWriter> writer = storage->MaybeCreateWriter(
-      GURL("https://origin1.test/dict"), now_time, *headers);
-  ASSERT_TRUE(writer);
-  writer->Finish();
+  WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*",
+                  {});
 
   // Check the returned dictionary from GetDictionary().
   std::unique_ptr<SharedDictionary> dict =
diff --git a/services/network/shared_dictionary/shared_dictionary_storage.h b/services/network/shared_dictionary/shared_dictionary_storage.h
index c31c638..0c431413 100644
--- a/services/network/shared_dictionary/shared_dictionary_storage.h
+++ b/services/network/shared_dictionary/shared_dictionary_storage.h
@@ -5,11 +5,16 @@
 #ifndef SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_H_
 #define SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_H_
 
+#include <map>
 #include <set>
+#include <string>
 
 #include "base/component_export.h"
 #include "base/memory/ref_counted.h"
+#include "base/strings/pattern.h"
 #include "base/time/time.h"
+#include "url/gurl.h"
+#include "url/scheme_host_port.h"
 
 class GURL;
 
@@ -23,7 +28,7 @@
 class SharedDictionaryWriter;
 
 // Shared Dictionary Storage manages dictionaries for a particular
-// net::NetworkIsolationKey.
+// net::SharedDictionaryStorageIsolationKey.
 class COMPONENT_EXPORT(NETWORK_SERVICE) SharedDictionaryStorage
     : public base::RefCounted<SharedDictionaryStorage> {
  public:
@@ -54,6 +59,40 @@
       const std::string& match) = 0;
 };
 
+// Returns a matching dictionary for `url` from `dictionary_info_map`.
+// This is a template method because SharedDictionaryStorageOnDisk and
+// SharedDictionaryStorageOnDisk are using different class for
+// DictionaryInfoType.
+template <class DictionaryInfoType>
+const DictionaryInfoType* GetMatchingDictionaryFromDictionaryInfoMap(
+    const std::map<url::SchemeHostPort,
+                   std::map<std::string, DictionaryInfoType>>&
+        dictionary_info_map,
+    const GURL& url) {
+  auto it = dictionary_info_map.find(url::SchemeHostPort(url));
+  if (it == dictionary_info_map.end()) {
+    return nullptr;
+  }
+  const DictionaryInfoType* info = nullptr;
+  size_t mached_path_size = 0;
+  // TODO(crbug.com/1413922): If there are multiple matching dictionaries, this
+  // method currently returns the dictionary with the longest path pattern. But
+  // we should have a detailed description about `best-matching` in the spec.
+  for (const auto& item : it->second) {
+    // TODO(crbug.com/1413922): base::MatchPattern() is treating '?' in the
+    // pattern as an wildcard. We need to introduce a new flag in
+    // base::MatchPattern() to treat '?' as a normal character.
+    // TODO(crbug.com/1413922): Need to check the expiration of the dictionary.
+    // TODO(crbug.com/1413922): Need support path expansion for relative paths.
+    if ((item.first.size() > mached_path_size) &&
+        base::MatchPattern(url.path(), item.first)) {
+      mached_path_size = item.first.size();
+      info = &item.second;
+    }
+  }
+  return info;
+}
+
 }  // namespace network
 
 #endif  // SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_H_
diff --git a/services/network/shared_dictionary/shared_dictionary_storage_in_memory.cc b/services/network/shared_dictionary/shared_dictionary_storage_in_memory.cc
index bcf44fa..e77d0691 100644
--- a/services/network/shared_dictionary/shared_dictionary_storage_in_memory.cc
+++ b/services/network/shared_dictionary/shared_dictionary_storage_in_memory.cc
@@ -22,27 +22,8 @@
 
 std::unique_ptr<SharedDictionary>
 SharedDictionaryStorageInMemory::GetDictionary(const GURL& url) {
-  auto it = dictionary_info_map_.find(url::SchemeHostPort(url));
-  if (it == dictionary_info_map_.end()) {
-    return nullptr;
-  }
-  const DictionaryInfo* info = nullptr;
-  size_t mached_path_size = 0;
-  // TODO(crbug.com/1413922): If there are multiple matching dictionaries, this
-  // method currently returns the dictionary with the longest path pattern. But
-  // we should have a detailed description about `best-matching` in the spec.
-  for (const auto& item : it->second) {
-    // TODO(crbug.com/1413922): base::MatchPattern() is treating '?' in the
-    // pattern as an wildcard. We need to introduce a new flag in
-    // base::MatchPattern() to treat '?' as a normal character.
-    // TODO(crbug.com/1413922): Need to check the expiration of the dictionary.
-    // TODO(crbug.com/1413922): Need support path expansion for relative paths.
-    if ((item.first.size() > mached_path_size) &&
-        base::MatchPattern(url.path(), item.first)) {
-      mached_path_size = item.first.size();
-      info = &item.second;
-    }
-  }
+  const DictionaryInfo* info =
+      GetMatchingDictionaryFromDictionaryInfoMap(dictionary_info_map_, url);
 
   if (!info) {
     return nullptr;
diff --git a/services/network/shared_dictionary/shared_dictionary_storage_on_disk.cc b/services/network/shared_dictionary/shared_dictionary_storage_on_disk.cc
new file mode 100644
index 0000000..9a4fd1e
--- /dev/null
+++ b/services/network/shared_dictionary/shared_dictionary_storage_on_disk.cc
@@ -0,0 +1,224 @@
+// 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 "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h"
+
+#include "base/functional/callback_helpers.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_id_helper.h"
+#include "net/base/io_buffer.h"
+#include "net/extras/sqlite/sqlite_persistent_shared_dictionary_store.h"
+#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
+#include "services/network/shared_dictionary/shared_dictionary_on_disk.h"
+#include "services/network/shared_dictionary/shared_dictionary_writer_on_disk.h"
+#include "url/scheme_host_port.h"
+
+namespace network {
+
+// This is a RefCounted subclass of SharedDictionaryOnDisk. This is used to
+// share a SharedDictionaryOnDisk for multiple concurrent network requests.
+class SharedDictionaryStorageOnDisk::RefCountedSharedDictionary
+    : public SharedDictionaryOnDisk,
+      public base::RefCounted<RefCountedSharedDictionary> {
+ public:
+  // `on_deleted_closure_runner` will be called when `this` is deleted.
+  RefCountedSharedDictionary(
+      size_t size,
+      const net::SHA256HashValue& hash,
+      const base::UnguessableToken& disk_cache_key_token,
+      SharedDictionaryDiskCache& disk_cahe,
+      base::ScopedClosureRunner on_deleted_closure_runner)
+      : SharedDictionaryOnDisk(size, hash, disk_cache_key_token, &disk_cahe),
+        on_deleted_closure_runner_(std::move(on_deleted_closure_runner)) {}
+
+ private:
+  friend class RefCounted<RefCountedSharedDictionary>;
+  ~RefCountedSharedDictionary() override = default;
+
+  base::ScopedClosureRunner on_deleted_closure_runner_;
+};
+
+// This is a subclass of SharedDictionaryOnDisk. This holds a reference to a
+// RefCountedSharedDictionary.
+class SharedDictionaryStorageOnDisk::WrappedSharedDictionary
+    : public SharedDictionary {
+ public:
+  explicit WrappedSharedDictionary(
+      scoped_refptr<RefCountedSharedDictionary> ref_counted_shared_dictionary)
+      : ref_counted_shared_dictionary_(
+            std::move(ref_counted_shared_dictionary)) {}
+
+  WrappedSharedDictionary(const WrappedSharedDictionary&) = delete;
+  WrappedSharedDictionary& operator=(const WrappedSharedDictionary&) = delete;
+
+  // SharedDictionary
+  int ReadAll(base::OnceCallback<void(int)> callback) override {
+    return ref_counted_shared_dictionary_->ReadAll(std::move(callback));
+  }
+  scoped_refptr<net::IOBuffer> data() const override {
+    return ref_counted_shared_dictionary_->data();
+  }
+  size_t size() const override {
+    return ref_counted_shared_dictionary_->size();
+  }
+  const net::SHA256HashValue& hash() const override {
+    return ref_counted_shared_dictionary_->hash();
+  }
+
+ private:
+  scoped_refptr<RefCountedSharedDictionary> ref_counted_shared_dictionary_;
+};
+
+SharedDictionaryStorageOnDisk::SharedDictionaryStorageOnDisk(
+    base::WeakPtr<SharedDictionaryManagerOnDisk> manager,
+    const net::SharedDictionaryStorageIsolationKey& isolation_key,
+    base::ScopedClosureRunner on_deleted_closure_runner)
+    : manager_(manager),
+      isolation_key_(isolation_key),
+      on_deleted_closure_runner_(std::move(on_deleted_closure_runner)) {
+  manager_->metadata_store().GetDictionaries(
+      isolation_key_,
+      base::BindOnce(&SharedDictionaryStorageOnDisk::OnDatabaseRead,
+                     weak_factory_.GetWeakPtr()));
+}
+
+SharedDictionaryStorageOnDisk::~SharedDictionaryStorageOnDisk() = default;
+
+std::unique_ptr<SharedDictionary> SharedDictionaryStorageOnDisk::GetDictionary(
+    const GURL& url) {
+  if (!manager_) {
+    return nullptr;
+  }
+  const net::SharedDictionaryInfo* info =
+      GetMatchingDictionaryFromDictionaryInfoMap(dictionary_info_map_, url);
+  if (!info) {
+    return nullptr;
+  }
+  auto it = dictionaries_.find(info->disk_cache_key_token());
+  if (it != dictionaries_.end()) {
+    CHECK_EQ(info->size(), it->second->size());
+    CHECK(info->hash() == it->second->hash());
+    return std::make_unique<WrappedSharedDictionary>(it->second.get());
+  }
+
+  auto ref_counted_shared_dictionary = base::MakeRefCounted<
+      RefCountedSharedDictionary>(
+      info->size(), info->hash(), info->disk_cache_key_token(),
+      manager_->disk_cache(),
+      base::ScopedClosureRunner(base::BindOnce(
+          &SharedDictionaryStorageOnDisk::OnRefCountedSharedDictionaryDeleted,
+          weak_factory_.GetWeakPtr(), info->disk_cache_key_token())));
+  dictionaries_.emplace(info->disk_cache_key_token(),
+                        ref_counted_shared_dictionary.get());
+  return std::make_unique<WrappedSharedDictionary>(
+      std::move(ref_counted_shared_dictionary));
+}
+
+scoped_refptr<SharedDictionaryWriter>
+SharedDictionaryStorageOnDisk::CreateWriter(const GURL& url,
+                                            base::Time response_time,
+                                            base::TimeDelta expiration,
+                                            const std::string& match) {
+  if (!manager_) {
+    return nullptr;
+  }
+  auto writer = base::MakeRefCounted<SharedDictionaryWriterOnDisk>(
+      base::BindOnce(
+          &SharedDictionaryStorageOnDisk::OnDictionaryWrittenInDiskCache,
+          weak_factory_.GetWeakPtr(), url, response_time, expiration, match),
+      manager_->disk_cache().GetWeakPtr());
+  writer->Initialize();
+  return writer;
+}
+
+void SharedDictionaryStorageOnDisk::OnDatabaseRead(
+    net::SQLitePersistentSharedDictionaryStore::Error error,
+    std::vector<net::SharedDictionaryInfo> info_list) {
+  CHECK(dictionary_info_map_.empty());
+  std::set<base::UnguessableToken> deleted_cache_tokens;
+  for (auto& info : info_list) {
+    const url::SchemeHostPort scheme_host_port =
+        url::SchemeHostPort(info.url());
+    const std::string match = info.match();
+    (dictionary_info_map_[scheme_host_port])
+        .insert(std::make_pair(match, std::move(info)));
+  }
+}
+
+void SharedDictionaryStorageOnDisk::OnDictionaryWrittenInDiskCache(
+    const GURL& url,
+    base::Time response_time,
+    base::TimeDelta expiration,
+    const std::string& match,
+    SharedDictionaryWriterOnDisk::Result result,
+    size_t size,
+    const net::SHA256HashValue& hash,
+    const base::UnguessableToken& disk_cache_key_token) {
+  if (!manager_) {
+    return;
+  }
+  if (result != SharedDictionaryWriterOnDisk::Result::kSuccess) {
+    return;
+  }
+  base::Time last_used_time = base::Time::Now();
+  net::SharedDictionaryInfo info(url, response_time, expiration, match,
+                                 last_used_time, size, hash,
+                                 disk_cache_key_token,
+                                 /*primary_key_in_database=*/absl::nullopt);
+
+  manager_->metadata_store().RegisterDictionary(
+      isolation_key_, info,
+      base::BindOnce(
+          &SharedDictionaryStorageOnDisk::OnDictionaryWrittenInDatabase,
+          weak_factory_.GetWeakPtr(), info));
+}
+
+void SharedDictionaryStorageOnDisk::OnDictionaryWrittenInDatabase(
+    net::SharedDictionaryInfo info,
+    net::SQLitePersistentSharedDictionaryStore::Error error,
+    absl::optional<int64_t> primary_key_in_database) {
+  if (!manager_) {
+    return;
+  }
+  if (error != net::SQLitePersistentSharedDictionaryStore::Error::kOk) {
+    manager_->disk_cache().DoomEntry(info.disk_cache_key_token().ToString(),
+                                     base::DoNothing());
+    return;
+  }
+
+  CHECK(primary_key_in_database.has_value());
+  info.set_primary_key_in_database(*primary_key_in_database);
+  url::SchemeHostPort scheme_host_port = url::SchemeHostPort(info.url());
+  auto it1 = dictionary_info_map_.find(scheme_host_port);
+  if (it1 == dictionary_info_map_.end()) {
+    dictionary_info_map_.insert(make_pair(
+        scheme_host_port, std::map<std::string, net::SharedDictionaryInfo>(
+                              {{info.match(), info}})));
+    return;
+  }
+
+  std::map<std::string, net::SharedDictionaryInfo>& match_info_map =
+      it1->second;
+  auto it2 = match_info_map.find(info.match());
+  if (it2 == match_info_map.end()) {
+    match_info_map.insert(make_pair(info.match(), info));
+    return;
+  }
+
+  manager_->disk_cache().DoomEntry(
+      it2->second.disk_cache_key_token().ToString(), base::DoNothing());
+  it2->second = info;
+}
+
+void SharedDictionaryStorageOnDisk::OnRefCountedSharedDictionaryDeleted(
+    const base::UnguessableToken& disk_cache_key_token) {
+  size_t removed_count = dictionaries_.erase(disk_cache_key_token);
+  CHECK_EQ(1U, removed_count);
+}
+
+}  // namespace network
diff --git a/services/network/shared_dictionary/shared_dictionary_storage_on_disk.h b/services/network/shared_dictionary/shared_dictionary_storage_on_disk.h
new file mode 100644
index 0000000..91977b5
--- /dev/null
+++ b/services/network/shared_dictionary/shared_dictionary_storage_on_disk.h
@@ -0,0 +1,98 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_ON_DISK_H_
+#define SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_ON_DISK_H_
+
+#include <map>
+#include <set>
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/hash_value.h"
+#include "net/base/network_isolation_key.h"
+#include "net/extras/shared_dictionary/shared_dictionary_info.h"
+#include "net/extras/shared_dictionary/shared_dictionary_storage_isolation_key.h"
+#include "net/extras/sqlite/sqlite_persistent_shared_dictionary_store.h"
+#include "services/network/shared_dictionary/shared_dictionary_storage.h"
+#include "services/network/shared_dictionary/shared_dictionary_writer_on_disk.h"
+#include "url/gurl.h"
+#include "url/scheme_host_port.h"
+
+namespace network {
+
+class SharedDictionaryManagerOnDisk;
+
+// A SharedDictionaryStorage which is managed by SharedDictionaryManagerOnDisk.
+class SharedDictionaryStorageOnDisk : public SharedDictionaryStorage {
+ public:
+  SharedDictionaryStorageOnDisk(
+      base::WeakPtr<SharedDictionaryManagerOnDisk> manager,
+      const net::SharedDictionaryStorageIsolationKey& isolation_key,
+      base::ScopedClosureRunner on_deleted_closure_runner);
+
+  SharedDictionaryStorageOnDisk(const SharedDictionaryStorageOnDisk&) = delete;
+  SharedDictionaryStorageOnDisk& operator=(
+      const SharedDictionaryStorageOnDisk&) = delete;
+
+  // SharedDictionaryStorage
+  std::unique_ptr<SharedDictionary> GetDictionary(const GURL& url) override;
+  scoped_refptr<SharedDictionaryWriter> CreateWriter(
+      const GURL& url,
+      base::Time response_time,
+      base::TimeDelta expiration,
+      const std::string& match) override;
+
+ protected:
+  ~SharedDictionaryStorageOnDisk() override;
+
+ private:
+  friend class SharedDictionaryManagerTest;
+  friend class SharedDictionaryManagerOnDiskTest;
+
+  class RefCountedSharedDictionary;
+  class WrappedSharedDictionary;
+
+  void OnDatabaseRead(net::SQLitePersistentSharedDictionaryStore::Error error,
+                      std::vector<net::SharedDictionaryInfo> info_list);
+  void OnDictionaryWrittenInDiskCache(
+      const GURL& url,
+      base::Time response_time,
+      base::TimeDelta expiration,
+      const std::string& match,
+      SharedDictionaryWriterOnDisk::Result result,
+      size_t size,
+      const net::SHA256HashValue& hash,
+      const base::UnguessableToken& disk_cache_key_token);
+  void OnDictionaryWrittenInDatabase(
+      net::SharedDictionaryInfo info,
+      net::SQLitePersistentSharedDictionaryStore::Error error,
+      absl::optional<int64_t> primary_key_in_database);
+
+  void OnRefCountedSharedDictionaryDeleted(
+      const base::UnguessableToken& disk_cache_key_token);
+
+  const std::map<url::SchemeHostPort,
+                 std::map<std::string, net::SharedDictionaryInfo>>&
+  GetDictionaryMapForTesting() {
+    return dictionary_info_map_;
+  }
+
+  base::WeakPtr<SharedDictionaryManagerOnDisk> manager_;
+  const net::SharedDictionaryStorageIsolationKey isolation_key_;
+  base::ScopedClosureRunner on_deleted_closure_runner_;
+  std::map<url::SchemeHostPort,
+           std::map<std::string, net::SharedDictionaryInfo>>
+      dictionary_info_map_;
+  std::map<base::UnguessableToken, raw_ptr<RefCountedSharedDictionary>>
+      dictionaries_;
+
+  base::WeakPtrFactory<SharedDictionaryStorageOnDisk> weak_factory_{this};
+};
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_ON_DISK_H_
diff --git a/services/network/shared_dictionary/shared_dictionary_writer_in_memory_unittest.cc b/services/network/shared_dictionary/shared_dictionary_writer_in_memory_unittest.cc
index ccfbc19..96bcc10 100644
--- a/services/network/shared_dictionary/shared_dictionary_writer_in_memory_unittest.cc
+++ b/services/network/shared_dictionary/shared_dictionary_writer_in_memory_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/shared_dictionary/shared_dictionary_writer_in_memory.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/test/bind.h"
 #include "crypto/secure_hash.h"
 #include "net/base/hash_value.h"
@@ -127,7 +128,8 @@
 }
 
 TEST(SharedDictionaryWriterInMemory, ErrorSizeExceedsLimit) {
-  shared_dictionary::SetDictionarySizeLimitForTesting(kTestData1.size());
+  base::ScopedClosureRunner size_limit_resetter =
+      shared_dictionary::SetDictionarySizeLimitForTesting(kTestData1.size());
 
   bool finish_callback_called = false;
   scoped_refptr<SharedDictionaryWriterInMemory> writer = base::MakeRefCounted<
diff --git a/services/network/shared_dictionary/shared_dictionary_writer_on_disk.cc b/services/network/shared_dictionary/shared_dictionary_writer_on_disk.cc
index f62146e..37e7bd6 100644
--- a/services/network/shared_dictionary/shared_dictionary_writer_on_disk.cc
+++ b/services/network/shared_dictionary/shared_dictionary_writer_on_disk.cc
@@ -101,9 +101,10 @@
     MaybeFinish();
     return;
   }
-  std::vector<scoped_refptr<net::StringIOBuffer>> buffers =
-      std::move(pending_write_buffers_);
-  for (auto buffer : buffers) {
+
+  while (!pending_write_buffers_.empty()) {
+    scoped_refptr<net::StringIOBuffer> buffer = *pending_write_buffers_.begin();
+    pending_write_buffers_.pop_front();
     WriteData(std::move(buffer));
   }
 }
diff --git a/services/network/shared_dictionary/shared_dictionary_writer_on_disk.h b/services/network/shared_dictionary/shared_dictionary_writer_on_disk.h
index 53ec889..32a4f7a 100644
--- a/services/network/shared_dictionary/shared_dictionary_writer_on_disk.h
+++ b/services/network/shared_dictionary/shared_dictionary_writer_on_disk.h
@@ -5,9 +5,9 @@
 #ifndef SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_WRITER_ON_DISK_H_
 #define SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_WRITER_ON_DISK_H_
 
+#include <deque>
 #include <set>
 #include <string>
-#include <vector>
 
 #include "base/component_export.h"
 #include "base/functional/callback.h"
@@ -80,7 +80,7 @@
 
   State state_ = State::kBeforeInitialize;
   disk_cache::ScopedEntryPtr entry_;
-  std::vector<scoped_refptr<net::StringIOBuffer>> pending_write_buffers_;
+  std::deque<scoped_refptr<net::StringIOBuffer>> pending_write_buffers_;
 
   int offset_ = 0;
   bool finish_called_ = false;
diff --git a/services/network/shared_dictionary/shared_dictionary_writer_on_disk_unittest.cc b/services/network/shared_dictionary/shared_dictionary_writer_on_disk_unittest.cc
index 75926c2..f0ce360 100644
--- a/services/network/shared_dictionary/shared_dictionary_writer_on_disk_unittest.cc
+++ b/services/network/shared_dictionary/shared_dictionary_writer_on_disk_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/shared_dictionary/shared_dictionary_writer_on_disk.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/stringprintf.h"
@@ -513,6 +514,76 @@
   writer.reset();
 }
 
+TEST_F(SharedDictionaryWriterOnDiskTest, MultipleWriteSyncWrite) {
+  auto disk_cache = std::make_unique<FakeSharedDictionaryDiskCache>(
+      CreateBackendResultType::kAsyncSuccess);
+  disk_cache->Initialize();
+
+  std::unique_ptr<disk_cache::EntryMock> entry =
+      std::make_unique<disk_cache::EntryMock>();
+
+  EXPECT_CALL(*entry, WriteData)
+      .WillOnce([&](int index, int offset, net::IOBuffer* buf, int buf_len,
+                    net::CompletionOnceCallback callback,
+                    bool truncate) -> int {
+        EXPECT_EQ(1, index);
+        EXPECT_EQ(0, offset);
+        EXPECT_EQ(base::checked_cast<int>(kTestData1.size()), buf_len);
+        EXPECT_EQ(
+            kTestData1,
+            std::string(reinterpret_cast<const char*>(buf->data()), buf_len));
+        return base::checked_cast<int>(kTestData1.size());
+      })
+      .WillOnce([&](int index, int offset, net::IOBuffer* buf, int buf_len,
+                    net::CompletionOnceCallback callback,
+                    bool truncate) -> int {
+        EXPECT_EQ(1, index);
+        EXPECT_EQ(base::checked_cast<int>(kTestData1.size()), offset);
+        EXPECT_EQ(base::checked_cast<int>(kTestData2.size()), buf_len);
+        EXPECT_EQ(
+            kTestData2,
+            std::string(reinterpret_cast<const char*>(buf->data()), buf_len));
+        return base::checked_cast<int>(kTestData2.size());
+      });
+
+  absl::optional<std::string> cache_key;
+  disk_cache::EntryResultCallback create_entry_callback;
+  EXPECT_CALL(*disk_cache->backend(), CreateEntry)
+      .WillOnce([&](const std::string& key, net::RequestPriority priority,
+                    disk_cache::EntryResultCallback callback) {
+        cache_key = key;
+        create_entry_callback = std::move(callback);
+        return disk_cache::EntryResult::MakeError(net::ERR_IO_PENDING);
+      });
+
+  bool finish_callback_called = false;
+  scoped_refptr<SharedDictionaryWriterOnDisk> writer =
+      base::MakeRefCounted<SharedDictionaryWriterOnDisk>(
+          base::BindLambdaForTesting(
+              [&](SharedDictionaryWriterOnDisk::Result result, size_t size,
+                  const net::SHA256HashValue& hash,
+                  const base::UnguessableToken& cache_key_token) {
+                EXPECT_EQ(SharedDictionaryWriterOnDisk::Result::kSuccess,
+                          result);
+                EXPECT_EQ(kTestData1.size() + kTestData2.size(), size);
+                EXPECT_EQ(*cache_key, cache_key_token.ToString());
+                EXPECT_EQ(GetHash(kTestData1 + kTestData2), hash);
+                finish_callback_called = true;
+              }),
+          disk_cache->GetWeakPtr());
+  writer->Initialize();
+  writer->Append(kTestData1.c_str(), kTestData1.size());
+  writer->Append(kTestData2.c_str(), kTestData2.size());
+  writer->Finish();
+
+  disk_cache->RunCreateCacheBackendCallback();
+  std::move(create_entry_callback)
+      .Run(disk_cache::EntryResult::MakeCreated(entry.release()));
+  EXPECT_TRUE(finish_callback_called);
+
+  writer.reset();
+}
+
 TEST_F(SharedDictionaryWriterOnDiskTest, AsyncWriteFailureOnMultipleWrites) {
   auto disk_cache = std::make_unique<FakeSharedDictionaryDiskCache>(
       CreateBackendResultType::kAsyncSuccess);
@@ -792,7 +863,8 @@
 }
 
 TEST_F(SharedDictionaryWriterOnDiskTest, ErrorSizeExceedsLimitBeforeOnEntry) {
-  shared_dictionary::SetDictionarySizeLimitForTesting(kTestData1.size());
+  base::ScopedClosureRunner size_limit_resetter =
+      shared_dictionary::SetDictionarySizeLimitForTesting(kTestData1.size());
 
   auto disk_cache = std::make_unique<FakeSharedDictionaryDiskCache>(
       CreateBackendResultType::kAsyncSuccess);
@@ -841,7 +913,8 @@
 }
 
 TEST_F(SharedDictionaryWriterOnDiskTest, ErrorSizeExceedsLimitAfterOnEntry) {
-  shared_dictionary::SetDictionarySizeLimitForTesting(kTestData1.size());
+  base::ScopedClosureRunner size_limit_resetter =
+      shared_dictionary::SetDictionarySizeLimitForTesting(kTestData1.size());
 
   auto disk_cache = std::make_unique<FakeSharedDictionaryDiskCache>(
       CreateBackendResultType::kAsyncSuccess);
diff --git a/services/shape_detection/text_detection_impl_mac_unittest.mm b/services/shape_detection/text_detection_impl_mac_unittest.mm
index 6ac882e..8292b43 100644
--- a/services/shape_detection/text_detection_impl_mac_unittest.mm
+++ b/services/shape_detection/text_detection_impl_mac_unittest.mm
@@ -8,9 +8,9 @@
 
 #include <memory>
 
+#include "base/apple/bridging.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
-#include "base/mac/bridging.h"
 #include "base/mac/mac_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/run_loop.h"
@@ -77,7 +77,7 @@
                                       attributes:attributes];
 
   base::ScopedCFTypeRef<CTLineRef> line(
-      CTLineCreateWithAttributedString(base::mac::NSToCFPtrCast(info)));
+      CTLineCreateWithAttributedString(base::apple::NSToCFPtrCast(info)));
 
   CGContextSetTextPosition(context, 10.0, height / 2.0);
   CTLineDraw(line, context);
diff --git a/services/video_capture/video_capture_service_impl.cc b/services/video_capture/video_capture_service_impl.cc
index 63ec76b..cb22f73 100644
--- a/services/video_capture/video_capture_service_impl.cc
+++ b/services/video_capture/video_capture_service_impl.cc
@@ -153,14 +153,19 @@
       return;
     }
 
-    if (!viz_gpu_->GetGpuChannel() || viz_gpu_->GetGpuChannel()->IsLost()) {
-      scoped_refptr<gpu::GpuChannelHost> gpu_channel_host =
-          viz_gpu_->EstablishGpuChannelSync();
+    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host =
+        viz_gpu_->GetGpuChannel();
+    if (!gpu_channel_host || gpu_channel_host->IsLost()) {
+      gpu_channel_host = viz_gpu_->EstablishGpuChannelSync();
+    }
+
+    if (!gpu_channel_host) {
+      return;
     }
 
     scoped_refptr<viz::ContextProvider> context_provider =
         base::MakeRefCounted<viz::ContextProviderCommandBuffer>(
-            viz_gpu_->GetGpuChannel(), viz_gpu_->GetGpuMemoryBufferManager(),
+            std::move(gpu_channel_host), viz_gpu_->GetGpuMemoryBufferManager(),
             0 /* stream ID */, gpu::SchedulingPriority::kNormal,
             gpu::kNullSurfaceHandle,
             GURL(std::string("chrome://gpu/VideoCapture")),
@@ -252,9 +257,11 @@
     gpu_dependencies_context_ = std::make_unique<GpuDependenciesContext>();
 
 #if BUILDFLAG(IS_LINUX)
-  if (!viz_gpu_context_provider_) {
-    viz_gpu_context_provider_ =
-        std::make_unique<VizGpuContextProvider>(std::move(viz_gpu_));
+  if (switches::IsVideoCaptureUseGpuMemoryBufferEnabled()) {
+    if (!viz_gpu_context_provider_) {
+      viz_gpu_context_provider_ =
+          std::make_unique<VizGpuContextProvider>(std::move(viz_gpu_));
+    }
   }
 #endif  // BUILDFLAG(IS_LINUX)
 }
diff --git a/services/viz/public/cpp/gpu/context_provider_command_buffer.cc b/services/viz/public/cpp/gpu/context_provider_command_buffer.cc
index 594fb8e..0d4dc9d 100644
--- a/services/viz/public/cpp/gpu/context_provider_command_buffer.cc
+++ b/services/viz/public/cpp/gpu/context_provider_command_buffer.cc
@@ -128,6 +128,7 @@
 gpu::ContextResult ContextProviderCommandBuffer::BindToCurrentSequence() {
   // This is called on the thread the context will be used.
   DCHECK(context_sequence_checker_.CalledOnValidSequence());
+  CHECK(channel_);
 
   if (bind_tried_)
     return bind_result_;
diff --git a/services/webnn/dml/command_queue.h b/services/webnn/dml/command_queue.h
index 4bba165e..ae808f25 100644
--- a/services/webnn/dml/command_queue.h
+++ b/services/webnn/dml/command_queue.h
@@ -8,9 +8,11 @@
 #include <DirectML.h>
 #include <d3d12.h>
 #include <wrl.h>
-#include <deque>
 
+#include <deque>
 #include <vector>
+
+#include "base/gtest_prod_util.h"
 #include "base/win/scoped_handle.h"
 
 namespace webnn::dml {
diff --git a/skia/ext/test_fonts_mac.mm b/skia/ext/test_fonts_mac.mm
index 0cf6a14..fd8b975 100644
--- a/skia/ext/test_fonts_mac.mm
+++ b/skia/ext/test_fonts_mac.mm
@@ -7,9 +7,9 @@
 #include <CoreText/CoreText.h>
 #include <Foundation/Foundation.h>
 
+#include "base/apple/bridging.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
-#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -32,7 +32,7 @@
 
   if (@available(macOS 10.15, *)) {
     CTFontManagerRegisterFontURLs(
-        base::mac::NSToCFPtrCast(font_urls), kCTFontManagerScopeProcess,
+        base::apple::NSToCFPtrCast(font_urls), kCTFontManagerScopeProcess,
         /*enabled=*/true, ^bool(CFArrayRef errors, bool done) {
           if (CFArrayGetCount(errors)) {
             DLOG(FATAL) << "Failed to activate fonts.";
@@ -40,9 +40,9 @@
           return true;
         });
   } else {
-    if (!CTFontManagerRegisterFontsForURLs(base::mac::NSToCFPtrCast(font_urls),
-                                           kCTFontManagerScopeProcess,
-                                           /*errors=*/nullptr)) {
+    if (!CTFontManagerRegisterFontsForURLs(
+            base::apple::NSToCFPtrCast(font_urls), kCTFontManagerScopeProcess,
+            /*errors=*/nullptr)) {
       DLOG(FATAL) << "Failed to activate fonts.";
     }
   }
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h
index c2076e0..8f1d28e6 100644
--- a/storage/browser/quota/quota_manager_impl.h
+++ b/storage/browser/quota/quota_manager_impl.h
@@ -18,6 +18,7 @@
 #include "base/component_export.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/ref_counted_delete_on_sequence.h"
 #include "base/memory/weak_ptr.h"
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h
index 8b008ed..5239854 100644
--- a/storage/browser/test/mock_quota_manager.h
+++ b/storage/browser/test/mock_quota_manager.h
@@ -13,6 +13,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "components/services/storage/public/mojom/quota_client.mojom.h"
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 1af5534..5225296 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -26627,7 +26627,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -26689,7 +26689,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -26753,7 +26753,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -26815,7 +26815,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -26869,7 +26869,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -26921,7 +26921,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -26982,7 +26982,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27043,7 +27043,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27105,7 +27105,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27167,7 +27167,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27229,7 +27229,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27290,7 +27290,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27352,7 +27352,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27413,7 +27413,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27474,7 +27474,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27541,7 +27541,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27602,7 +27602,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27741,7 +27741,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27808,7 +27808,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27870,7 +27870,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -27932,7 +27932,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28058,7 +28058,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28120,7 +28120,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28182,7 +28182,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28243,7 +28243,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28304,7 +28304,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28365,7 +28365,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28427,7 +28427,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28488,7 +28488,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28549,7 +28549,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28610,7 +28610,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28671,7 +28671,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28735,7 +28735,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28796,7 +28796,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28857,7 +28857,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28918,7 +28918,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -28979,7 +28979,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29040,7 +29040,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29101,7 +29101,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29162,7 +29162,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29223,7 +29223,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29284,7 +29284,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29345,7 +29345,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29406,7 +29406,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29467,7 +29467,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29528,7 +29528,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29589,7 +29589,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29650,7 +29650,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29713,7 +29713,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29775,7 +29775,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29838,7 +29838,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29900,7 +29900,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -29961,7 +29961,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30022,7 +30022,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30083,7 +30083,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30144,7 +30144,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30205,7 +30205,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30266,7 +30266,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30327,7 +30327,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30388,7 +30388,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30450,7 +30450,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30511,7 +30511,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30573,7 +30573,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30635,7 +30635,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30704,7 +30704,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30829,7 +30829,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30890,7 +30890,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -30951,7 +30951,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -31186,7 +31186,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -31406,7 +31406,7 @@
               "device_os_flavor": null,
               "device_playstore_version": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -31602,7 +31602,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -31652,7 +31652,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -31699,7 +31699,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
diff --git a/testing/buildbot/chromium.angle.json b/testing/buildbot/chromium.angle.json
index 3121e4d..8e7c8a8 100644
--- a/testing/buildbot/chromium.angle.json
+++ b/testing/buildbot/chromium.angle.json
@@ -203,11 +203,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -410,11 +416,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -618,11 +630,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -927,11 +945,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 404492e..ebf9100 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5730,9 +5730,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_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5743,8 +5743,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -5895,9 +5895,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5908,8 +5908,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -6042,9 +6042,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6055,8 +6055,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 88493059..de0844f 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -4881,7 +4881,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-22.04",
+              "os": "Ubuntu-18.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -7714,7 +7714,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-22.04",
+              "os": "Ubuntu-18.04",
               "pool": "chromium.tests.avd"
             }
           ],
@@ -25491,9 +25491,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_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25504,8 +25504,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -25656,9 +25656,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25669,8 +25669,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -25803,9 +25803,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25816,8 +25816,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.dawn.json b/testing/buildbot/chromium.dawn.json
index 6a07fb8..5b7a11b 100644
--- a/testing/buildbot/chromium.dawn.json
+++ b/testing/buildbot/chromium.dawn.json
@@ -474,11 +474,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -990,11 +996,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -1504,11 +1516,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -2020,11 +2038,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -2547,11 +2571,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -3093,11 +3123,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -3657,11 +3693,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -4200,11 +4242,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -4746,11 +4794,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -5270,11 +5324,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -5721,11 +5781,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -6129,11 +6195,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -6685,11 +6757,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -7093,11 +7171,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -7615,11 +7699,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -8051,11 +8141,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -8227,45 +8323,6 @@
       },
       {
         "args": [
-          "webgpu_cts",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--stable-jobs",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
-          "--enable-dawn-backend-validation",
-          "--use-webgpu-power-preference=default-high-performance",
-          "--jobs=4"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "webgpu_cts_with_validation_tests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "display_attached": "1",
-              "gpu": "10de:2184-27.21.14.5638",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "idempotent": false,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 14
-        },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
-      },
-      {
-        "args": [
           "--flag-specific=webgpu-swiftshader",
           "--initialize-webgpu-adapter-at-startup-timeout-ms=60000"
         ],
@@ -8603,11 +8660,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -9039,11 +9102,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -9215,45 +9284,6 @@
       },
       {
         "args": [
-          "webgpu_cts",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--stable-jobs",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
-          "--enable-dawn-backend-validation",
-          "--use-webgpu-power-preference=default-high-performance",
-          "--jobs=4"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "webgpu_cts_with_validation_tests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "display_attached": "1",
-              "gpu": "10de:2184-27.21.14.5638",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "idempotent": false,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 14
-        },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
-      },
-      {
-        "args": [
           "--flag-specific=webgpu-swiftshader",
           "--initialize-webgpu-adapter-at-startup-timeout-ms=60000"
         ],
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 2a42464..d4b3b6e 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -3248,6 +3248,1016 @@
       }
     ]
   },
+  "android-backuprefptr-arm-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_common_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_platform_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webkit_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webkit_unit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cast_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cc_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 15
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "wtf_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
+  "android-backuprefptr-arm64-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_common_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_platform_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webkit_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webkit_unit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cast_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cc_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 15
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "wtf_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
   "android-fieldtrial-rel": {
     "gtest_tests": [
       {
@@ -37552,6 +38562,232 @@
       }
     ]
   },
+  "linux-backuprefptr-x64-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
   "linux-blink-animation-use-time-delta": {
     "additional_compile_targets": [
       "all"
@@ -40146,9 +41382,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_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40158,8 +41394,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -40311,9 +41547,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40323,8 +41559,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -40458,9 +41694,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40470,8 +41706,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -41935,9 +43171,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_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41947,8 +43183,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -42100,9 +43336,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42112,8 +43348,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -42247,9 +43483,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42259,8 +43495,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -42995,9 +44231,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_v115.0.5764.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43007,8 +44243,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -45870,6 +47106,298 @@
       }
     ]
   },
+  "mac-backuprefptr-x64-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-10.15"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
+  "mac-cr23-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=ChromeRefresh2023"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=ChromeRefresh2023"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=ChromeRefresh2023"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "views_unittests",
+        "test_id_prefix": "ninja://ui/views:views_unittests/"
+      }
+    ]
+  },
   "mac-fieldtrial-tester": {
     "gtest_tests": [
       {
@@ -48141,6 +49669,524 @@
       }
     ]
   },
+  "win-backuprefptr-x64-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
+  "win-backuprefptr-x86-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-19045"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
+  "win-cr23-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--enable-features=ChromeRefresh2023"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=ChromeRefresh2023"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      },
+      {
+        "args": [
+          "--enable-features=ChromeRefresh2023"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "views_unittests",
+        "test_id_prefix": "ninja://ui/views:views_unittests/"
+      }
+    ]
+  },
   "win-fieldtrial-rel": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/chromium.goma.fyi.json b/testing/buildbot/chromium.goma.fyi.json
index 41407fa..28bb284e 100644
--- a/testing/buildbot/chromium.goma.fyi.json
+++ b/testing/buildbot/chromium.goma.fyi.json
@@ -42,47 +42,6 @@
       }
     ]
   },
-  "Mac Builder (dbg) Goma RBE Latest Client (clobber)": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-10.13"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-10.13"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
   "Mac M1 Builder (dbg) Goma RBE Canary (clobber)": {
     "additional_compile_targets": [
       "all"
@@ -129,41 +88,21 @@
       "chromedriver_webview_shell_apk"
     ]
   },
-  "android-archive-dbg-goma-rbe-ats-latest": {
-    "additional_compile_targets": [
-      "chromedriver_webview_shell_apk"
-    ]
-  },
   "android-archive-dbg-goma-rbe-canary": {
     "additional_compile_targets": [
       "chromedriver_webview_shell_apk"
     ]
   },
-  "android-archive-dbg-goma-rbe-latest": {
-    "additional_compile_targets": [
-      "chromedriver_webview_shell_apk"
-    ]
-  },
   "chromeos-amd64-generic-rel-goma-rbe-canary": {
     "additional_compile_targets": [
       "chromiumos_preflight"
     ]
   },
-  "chromeos-amd64-generic-rel-goma-rbe-latest": {
-    "additional_compile_targets": [
-      "chromiumos_preflight"
-    ]
-  },
   "ios-device-goma-rbe-canary-clobber": {
     "additional_compile_targets": [
       "all"
     ]
   },
-  "ios-device-goma-rbe-latest-clobber": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "linux-archive-rel-goma-rbe-ats-canary": {
     "additional_compile_targets": [
       "all"
@@ -203,45 +142,6 @@
       }
     ]
   },
-  "linux-archive-rel-goma-rbe-ats-latest": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
   "linux-archive-rel-goma-rbe-canary": {
     "additional_compile_targets": [
       "all"
@@ -281,45 +181,6 @@
       }
     ]
   },
-  "linux-archive-rel-goma-rbe-latest": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
   "mac-archive-rel-goma-rbe-canary": {
     "additional_compile_targets": [
       "chrome"
@@ -360,46 +221,5 @@
         "test_id_prefix": "ninja://content/test:content_unittests/"
       }
     ]
-  },
-  "mac-archive-rel-goma-rbe-latest": {
-    "additional_compile_targets": [
-      "chrome"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-10.13"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-10.13"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
   }
 }
diff --git a/testing/buildbot/chromium.goma.json b/testing/buildbot/chromium.goma.json
index ce40fff..2afaeb21 100644
--- a/testing/buildbot/chromium.goma.json
+++ b/testing/buildbot/chromium.goma.json
@@ -1,16 +1,6 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
-  "Chromium Android ARM 32-bit Goma RBE ToT": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
-  "Chromium Android ARM 32-bit Goma RBE ToT (ATS)": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "Chromium Linux Goma RBE Staging": {
     "additional_compile_targets": [
       "all"
@@ -50,45 +40,6 @@
       }
     ]
   },
-  "Chromium Linux Goma RBE Staging (clobber)": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
   "Chromium Linux Goma RBE Staging (dbg)": {
     "additional_compile_targets": [
       "all"
@@ -128,45 +79,6 @@
       }
     ]
   },
-  "Chromium Linux Goma RBE Staging (dbg) (clobber)": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
   "Chromium Mac Goma RBE Staging": {
     "additional_compile_targets": [
       "chrome"
@@ -208,47 +120,6 @@
       }
     ]
   },
-  "Chromium Mac Goma RBE Staging (clobber)": {
-    "additional_compile_targets": [
-      "chrome"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
   "Chromium Mac Goma RBE Staging (dbg)": {
     "additional_compile_targets": [
       "chrome"
@@ -290,60 +161,9 @@
       }
     ]
   },
-  "Chromium Mac Goma RBE ToT": {
-    "additional_compile_targets": [
-      "chrome"
-    ],
-    "gtest_tests": [
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      }
-    ]
-  },
-  "Chromium iOS Goma RBE ToT": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "chromeos-amd64-generic-rel-goma-rbe-staging": {
     "additional_compile_targets": [
       "chromiumos_preflight"
     ]
-  },
-  "chromeos-amd64-generic-rel-goma-rbe-tot": {
-    "additional_compile_targets": [
-      "chromiumos_preflight"
-    ]
   }
 }
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 8469277..ec26343b 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -76,11 +76,17 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -2838,11 +2844,17 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -3374,11 +3386,17 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -3883,11 +3901,17 @@
           "--use-gpu-in-tests",
           "--stop-ui",
           "--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.gl_unittests.filter",
-          "--magic-vm-cache=magic_cros_vm_cache"
+          "--magic-vm-cache=magic_cros_vm_cache",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -4671,11 +4695,17 @@
         "args": [
           "--use-gpu-in-tests",
           "--stop-ui",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.gl_unittests.filter"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.gl_unittests.filter",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -6191,11 +6221,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -6682,11 +6718,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -6811,11 +6853,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -7364,11 +7412,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -7903,11 +7957,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -8510,11 +8570,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -9154,11 +9220,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -9678,11 +9750,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -10450,11 +10528,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -11300,11 +11384,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -12069,11 +12159,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -12827,11 +12923,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -13509,11 +13611,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -14062,11 +14170,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -14774,10 +14888,235 @@
     ]
   },
   "Mac FYI Retina Release (Apple M2)": {
+    "gtest_tests": [
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "--enable-gpu",
+          "--test-launcher-bot-mode",
+          "--test-launcher-jobs=1",
+          "--gtest_filter=TabCaptureApiPixelTest.EndToEnd*"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "tab_capture_end2end_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--use-cmd-decoder=passthrough",
+          "--use-gl=angle",
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gl_tests_passthrough",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "gl_tests",
+        "test_id_prefix": "ninja://gpu:gl_tests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_unittests",
+        "test_id_prefix": "ninja://ui/gl:gl_unittests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gles2_conform_test",
+        "test_id_prefix": "ninja://gpu/gles2_conform_support:gles2_conform_test/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--gtest_filter=*Detection*",
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      }
+    ],
     "isolated_scripts": [
       {
         "args": [
-          "noop_sleep",
+          "context_lost",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "gpu_process",
           "--show-stdout",
           "--browser=release",
           "--passthrough",
@@ -14789,7 +15128,544 @@
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "noop_sleep_tests",
+        "name": "gpu_process_launch_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "hardware_accelerated_feature_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "info_collection",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
+          "--expected-vendor-id",
+          "106b",
+          "--expected-device-id",
+          "0"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "info_collection_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_pixel_passthrough_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "mediapipe",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=passthrough --use-gl=angle"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mediapipe_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_skia_gold_passthrough_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "trace_test",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "trace_test",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webcodecs",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webcodecs_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl2_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json",
+          "--jobs=4"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_gl_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl2_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=metal --use-cmd-decoder=passthrough --enable-features=EGLDualGPURendering,ForceHighPerformanceGPUForWebGL",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json",
+          "--jobs=4"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_metal_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl1_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
+          "--jobs=4"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_gl_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl1_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=metal --use-cmd-decoder=passthrough --enable-features=EGLDualGPURendering,ForceHighPerformanceGPUForWebGL",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
+          "--jobs=4"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_metal_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "display_attached": "1",
+              "hidpi": "1",
+              "mac_model": "Mac14,7",
+              "os": "Mac-13.3.1",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl1_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=swiftshader --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--test-filter=conformance/rendering/gl-drawelements.html",
+          "--jobs=4"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_swangle_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "should_retry_with_patch": false,
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -14896,11 +15772,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -15607,11 +16489,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -16215,11 +17103,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -16966,11 +17860,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -17734,11 +18634,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -18520,11 +19426,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -19299,11 +20211,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index bdb9010..e630411 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -414,11 +414,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -834,11 +840,17 @@
       {
         "args": [
           "--use-gpu-in-tests",
-          "--no-xvfb"
+          "--no-xvfb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -1251,11 +1263,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -1705,11 +1723,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -2136,11 +2160,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -2616,11 +2646,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -3090,11 +3126,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
@@ -3593,11 +3635,17 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests"
+          "--use-gpu-in-tests",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index e45a334d..707e741 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18080,12 +18080,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_v115.0.5764.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18096,8 +18096,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -18265,12 +18265,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18281,8 +18281,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
@@ -18427,12 +18427,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 115.0.5764.0",
+        "description": "Run with ash-chrome version 115.0.5766.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18443,8 +18443,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5764.0",
-              "revision": "version:115.0.5764.0"
+              "location": "lacros_version_skew_tests_v115.0.5766.0",
+              "revision": "version:115.0.5766.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 88aaadd..ff13fdc 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1247,6 +1247,11 @@
           'shards': 60,
         },
       },
+      'win-backuprefptr-x64-fyi-rel': {
+       'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/win_backuprefptr_fyi.browser_tests.filter',
+        ],
+      },
       'win-rel-cft': {
         'args': [
           # crbug.com/868082
@@ -1440,6 +1445,8 @@
             {
               # use 8-core to shorten runtime
               'cores': '8',
+              # TODO(crbug.com/1412588): Move to jammy.
+              'os': 'Ubuntu-18.04',
             },
           ],
         },
@@ -1477,6 +1484,8 @@
             {
               # use 8-core to shorten runtime
               'cores': '8',
+              # TODO(crbug.com/1412588): Move to jammy.
+              'os': 'Ubuntu-18.04',
             },
           ],
         },
@@ -1843,8 +1852,6 @@
             {
               # use 8-core to shorten runtime
               'cores': '8',
-              # TODO(crbug.com/1412588): Move the remainder to Jammy.
-              'os': 'Ubuntu-22.04',
             },
           ],
         },
@@ -1875,8 +1882,6 @@
             {
               # use 8-core to shorten runtime
               'cores': '8',
-              # TODO(crbug.com/1412588): Move the remainder to Jammy.
-              'os': 'Ubuntu-22.04',
             },
           ],
         },
@@ -2558,17 +2563,25 @@
       },
       'Mac FYI Experimental Release (Apple M1)': {
         'args': {
-          # The GPU information is not yet exposed in swarming, so we can't
-          # explicitly request it and have the JSON generation handle this
-          # automatically.
+          # Apple silicon does not show up normally as a PCI-e device, but
+          # Chrome is able to find a vendor ID that matches other (non-GPU)
+          # Apple PCI-e devices.
           '--expected-vendor-id': '106b',
         },
       },
       'Mac FYI Release (Apple M1)': {
         'args': {
-          # The GPU information is not yet exposed in swarming, so we can't
-          # explicitly request it and have the JSON generation handle this
-          # automatically.
+          # Apple silicon does not show up normally as a PCI-e device, but
+          # Chrome is able to find a vendor ID that matches other (non-GPU)
+          # Apple PCI-e devices.
+          '--expected-vendor-id': '106b',
+        },
+      },
+      'Mac FYI Retina Release (Apple M2)': {
+        'args': {
+          # Apple silicon does not show up normally as a PCI-e device, but
+          # Chrome is able to find a vendor ID that matches other (non-GPU)
+          # Apple PCI-e devices.
           '--expected-vendor-id': '106b',
         },
       },
@@ -4286,8 +4299,13 @@
       # Remove from bots where capacity is constrained.
       'Dawn Win10 x64 DEPS Release (Intel)',
       'Dawn Win10 x64 Release (Intel)',
+      # Disable testing with validation on x86 where they frequently OOM.
+      # See crbug.com/1444815.
       'Dawn Win10 x86 DEPS Release (Intel)',
       'Dawn Win10 x86 Release (Intel)',
+      'Dawn Win10 x86 DEPS Release (NVIDIA)',
+      'Dawn Win10 x86 Release (NVIDIA)',
+      # Don't need validation layers on code coverage bots
       'linux-code-coverage',
       'mac-code-coverage',
       'win10-code-coverage',
@@ -4476,12 +4494,6 @@
           '--use-persistent-shell',
         ],
         'swarming': {
-          'dimension_sets': [
-            {
-              # TODO(crbug.com/1412588): Move the remainder to Jammy.
-              'os': 'Ubuntu-22.04',
-            },
-          ],
           'shards': 27,
         },
       },
@@ -4508,12 +4520,6 @@
           '--use-persistent-shell',
         ],
         'swarming': {
-          'dimension_sets': [
-            {
-              # TODO(crbug.com/1412588): Move the remainder to Jammy.
-              'os': 'Ubuntu-22.04',
-            },
-          ],
           'shards': 27,
         },
       },
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index d6a18e1..b7a67d8 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -295,6 +295,44 @@
       'wm_unittests': {},
     },
 
+    'backuprefptr_generic_gtests': {
+      'base_unittests': {},
+      'blink_common_unittests': {},
+      'blink_platform_unittests': {},
+      'cast_unittests': {},
+      'cc_unittests': {},
+      'components_unittests': {
+        'android_swarming': {
+          'shards': 6,
+        },
+      },
+      'content_browsertests': {
+        'android_swarming': {
+          'shards': 15,
+        },
+        'swarming': {
+          'shards': 8,
+        },
+      },
+      'content_unittests': {
+        'android_swarming': {
+          'shards': 3,
+        },
+      },
+      'unit_tests': {
+        'android_swarming': {
+          'shards': 2,
+        },
+      },
+      'webkit_unit_tests': {
+        'test': 'blink_unittests',
+        'android_swarming': {
+          'shards': 6,
+        },
+      },
+      'wtf_unittests': {},
+    },
+
     'bfcache_android_specific_gtests': {
       'bf_cache_android_browsertests': {
         'args': [
@@ -2544,6 +2582,9 @@
         'linux_args': [
           '--no-xvfb',
         ],
+        'mixins': [
+          'skia_gold_test',
+        ],
       },
     },
     'gpu_common_gtests_validating': {
@@ -2572,6 +2613,9 @@
         'linux_args': [
           '--no-xvfb',
         ],
+        'mixins': [
+          'skia_gold_test',
+        ],
       },
     },
 
@@ -5877,6 +5921,10 @@
       'webengine_android_gtests',
     ],
 
+    'backuprefptr_gtests': [
+      'backuprefptr_generic_gtests',
+    ],
+
     'bfcache_android_gtests': [
       'bfcache_android_specific_gtests',
       'bfcache_generic_gtests',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 50e0a85..c78040c 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5764.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5766.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 115.0.5764.0',
+    'description': 'Run with ash-chrome version 115.0.5766.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v115.0.5764.0',
-          'revision': 'version:115.0.5764.0',
+          'location': 'lacros_version_skew_tests_v115.0.5766.0',
+          'revision': 'version:115.0.5766.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 99b25c8e..0602f1c 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1000,7 +1000,7 @@
           'isolate_profile_data',
           'nougat-x86-emulator',
           'emulator-4-cores',
-          'linux-bionic',
+          'linux-jammy',
           'x86-64',
         ],
         'additional_compile_targets': [
@@ -3186,6 +3186,34 @@
           'scripts': 'chromium_win_scripts',
         },
       },
+      'android-backuprefptr-arm-fyi-rel': {
+        'test_suites': {
+          'gtest_tests': 'backuprefptr_gtests',
+        },
+        'mixins': [
+          'has_native_resultdb_integration',
+          'pie_fleet',
+          'walleye',
+        ],
+        'os_type': 'android',
+        'gtest_args': [
+          '--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer',
+        ],
+      },
+      'android-backuprefptr-arm64-fyi-rel': {
+        'test_suites': {
+          'gtest_tests': 'backuprefptr_gtests',
+        },
+        'mixins': [
+          'has_native_resultdb_integration',
+          'pie_fleet',
+          'walleye',
+        ],
+        'os_type': 'android',
+        'gtest_args': [
+          '--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer',
+        ],
+      },
       'android-fieldtrial-rel': {
         'mixins': [
           'has_native_resultdb_integration',
@@ -3471,6 +3499,18 @@
           'scripts': 'test_traffic_annotation_auditor_script',
         },
       },
+      'linux-backuprefptr-x64-fyi-rel': {
+        'test_suites': {
+          'gtest_tests': 'backuprefptr_gtests',
+        },
+        'mixins': [
+          'linux-bionic',
+          'x86-64',
+        ],
+        'gtest_args': [
+          '--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer',
+        ],
+      },
       'linux-blink-animation-use-time-delta': {
         'mixins': [
           'linux-bionic',
@@ -3676,6 +3716,27 @@
           'isolated_scripts': 'wpt_web_tests_input',
         },
       },
+      'mac-backuprefptr-x64-fyi-rel': {
+        'test_suites': {
+          'gtest_tests': 'backuprefptr_gtests',
+        },
+        'mixins': [
+          'mac_10.15',
+          'x86-64',
+        ],
+        'gtest_args': [
+          '--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer',
+        ],
+      },
+      'mac-cr23-rel': {
+        'test_suites': {
+          'gtest_tests': 'cr23_gtests',
+        },
+        'mixins': [
+          'mac_x64',
+        ],
+        'os_type': 'mac',
+      },
       'mac-fieldtrial-tester': {
         'mixins': [
           'finch-chromium-swarming-pool',
@@ -3782,6 +3843,40 @@
           'scripts': 'test_traffic_annotation_auditor_script',
         },
       },
+      'win-backuprefptr-x64-fyi-rel': {
+        'test_suites': {
+          'gtest_tests': 'backuprefptr_gtests',
+        },
+        'mixins': [
+          'win10',
+          'x86-64',
+        ],
+        'gtest_args': [
+          '--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer',
+        ],
+      },
+      'win-backuprefptr-x86-fyi-rel': {
+        'test_suites': {
+          'gtest_tests': 'backuprefptr_gtests',
+        },
+        'mixins': [
+          'win10',
+          'x86-64',
+        ],
+        'gtest_args': [
+          '--enable-features=PartitionAllocBackupRefPtr:enabled-processes/non-renderer',
+        ],
+      },
+      'win-cr23-rel': {
+        'test_suites': {
+          'gtest_tests': 'cr23_gtests',
+        },
+        'mixins': [
+          'win10-any',
+          'x86-64',
+        ],
+        'os_type': 'win',
+      },
       'win-fieldtrial-rel': {
         'mixins': [
             'win10',
@@ -3865,16 +3960,6 @@
     'name': 'chromium.goma',
     'mixins': ['chromium-tester-service-account'],
     'machines': {
-      'Chromium Android ARM 32-bit Goma RBE ToT': {
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
-      'Chromium Android ARM 32-bit Goma RBE ToT (ATS)': {
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
       'Chromium Linux Goma RBE Staging': {
         'mixins': [
           'linux-bionic',
@@ -3886,17 +3971,6 @@
           'gtest_tests': 'goma_gtests',
         },
       },
-      'Chromium Linux Goma RBE Staging (clobber)': {
-        'mixins': [
-          'linux-bionic',
-        ],
-        'additional_compile_targets': [
-          'all',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_gtests',
-        },
-      },
       'Chromium Linux Goma RBE Staging (dbg)': {
         'mixins': [
           'linux-bionic',
@@ -3908,17 +3982,6 @@
           'gtest_tests': 'goma_gtests',
         },
       },
-      'Chromium Linux Goma RBE Staging (dbg) (clobber)': {
-        'mixins': [
-          'linux-bionic',
-        ],
-        'additional_compile_targets': [
-          'all',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_gtests',
-        },
-      },
       # Due to disk shortage on Mac, we build 'chrome' instead of 'all'.
       # See crbug.com/899425
       'Chromium Mac Goma RBE Staging': {
@@ -3932,17 +3995,6 @@
           'gtest_tests': 'goma_gtests',
         },
       },
-      'Chromium Mac Goma RBE Staging (clobber)': {
-        'mixins': [
-          'mac_x64',
-        ],
-        'additional_compile_targets': [
-          'chrome',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_gtests',
-        },
-      },
       'Chromium Mac Goma RBE Staging (dbg)': {
         'mixins': [
           'mac_x64',
@@ -3954,22 +4006,6 @@
           'gtest_tests': 'goma_gtests',
         },
       },
-      'Chromium Mac Goma RBE ToT': {
-        'mixins': [
-          'mac_x64',
-        ],
-        'additional_compile_targets': [
-          'chrome',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_gtests',
-        },
-      },
-      'Chromium iOS Goma RBE ToT': {
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
       # ChromeOS (simple chrome) builders.
       # Note:
       # chromeos-amd64-generic-rel-goma-* builders mirror targets of
@@ -3979,11 +4015,6 @@
           'chromiumos_preflight',
         ],
       },
-      'chromeos-amd64-generic-rel-goma-rbe-tot': {
-        'additional_compile_targets': [
-          'chromiumos_preflight',
-        ],
-      },
     },
   },
   {
@@ -3998,14 +4029,6 @@
           'gtest_tests': 'goma_mac_gtests',
         },
       },
-      'Mac Builder (dbg) Goma RBE Latest Client (clobber)': {
-        'additional_compile_targets': [
-          'all',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_mac_gtests',
-        },
-      },
       'Mac M1 Builder (dbg) Goma RBE Canary (clobber)': {
         'additional_compile_targets': [
           'all',
@@ -4027,21 +4050,11 @@
           'chromedriver_webview_shell_apk',
         ],
       },
-      'android-archive-dbg-goma-rbe-ats-latest': {
-        'additional_compile_targets': [
-          'chromedriver_webview_shell_apk',
-        ],
-      },
       'android-archive-dbg-goma-rbe-canary': {
         'additional_compile_targets': [
           'chromedriver_webview_shell_apk',
         ],
       },
-      'android-archive-dbg-goma-rbe-latest': {
-        'additional_compile_targets': [
-          'chromedriver_webview_shell_apk',
-        ],
-      },
       # Note:
       # chromeos-amd64-generic-rel-goma-* builders mirror targets of
       # chromeos-amd64-generic-rel.
@@ -4050,21 +4063,11 @@
           'chromiumos_preflight',
         ],
       },
-      'chromeos-amd64-generic-rel-goma-rbe-latest': {
-        'additional_compile_targets': [
-          'chromiumos_preflight',
-        ],
-      },
       'ios-device-goma-rbe-canary-clobber': {
         'additional_compile_targets': [
           'all',
         ]
       },
-      'ios-device-goma-rbe-latest-clobber': {
-        'additional_compile_targets': [
-          'all',
-        ]
-      },
       'linux-archive-rel-goma-rbe-ats-canary': {
         'additional_compile_targets': [
           'all',
@@ -4076,17 +4079,6 @@
           'gtest_tests': 'goma_gtests',
         },
       },
-      'linux-archive-rel-goma-rbe-ats-latest': {
-        'additional_compile_targets': [
-          'all',
-        ],
-        'mixins': [
-          'linux-bionic',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_gtests',
-        },
-      },
       'linux-archive-rel-goma-rbe-canary': {
         'additional_compile_targets': [
           'all',
@@ -4098,17 +4090,6 @@
           'gtest_tests': 'goma_gtests',
         },
       },
-      'linux-archive-rel-goma-rbe-latest': {
-        'additional_compile_targets': [
-          'all',
-        ],
-        'mixins': [
-          'linux-bionic',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_gtests',
-        },
-      },
       'mac-archive-rel-goma-rbe-canary': {
         'additional_compile_targets': [
           # Due to disk shortage, we build 'chrome' instead of 'all'.
@@ -4119,16 +4100,6 @@
           'gtest_tests': 'goma_mac_gtests',
         },
       },
-      'mac-archive-rel-goma-rbe-latest': {
-        'additional_compile_targets': [
-          # Due to disk shortage, we build 'chrome' instead of 'all'.
-          # See crbug.com/899425
-          'chrome',
-        ],
-        'test_suites': {
-          'gtest_tests': 'goma_mac_gtests',
-        },
-      },
     },
   },
   {
@@ -4674,7 +4645,8 @@
           'mac_arm64_apple_m2_retina_gpu_stable',
         ],
         'test_suites': {
-          'gpu_telemetry_tests': 'gpu_noop_sleep_telemetry_test',
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
         },
       },
       'Mac FYI Retina Release (NVIDIA)': {
diff --git a/testing/scripts/run_finch_smoke_tests_android.py b/testing/scripts/run_finch_smoke_tests_android.py
index f3f2f621..1437b50 100755
--- a/testing/scripts/run_finch_smoke_tests_android.py
+++ b/testing/scripts/run_finch_smoke_tests_android.py
@@ -78,7 +78,7 @@
 from py_utils.tempfile_ext import NamedTemporaryDirectory
 from scripts import common
 from skia_gold_common.skia_gold_properties import SkiaGoldProperties
-from skia_gold_infra import finch_skia_gold_session_manager
+from skia_gold_common import skia_gold_session_manager
 from skia_gold_infra import finch_skia_gold_utils
 from run_wpt_tests import get_device
 
@@ -334,7 +334,7 @@
     self._device.adb.Emu(['power', 'ac', 'on'])
     self._skia_gold_tmp_dir = tempfile.mkdtemp()
     self._skia_gold_session_manager = (
-        finch_skia_gold_session_manager.FinchSkiaGoldSessionManager(
+        skia_gold_session_manager.SkiaGoldSessionManager(
             self._skia_gold_tmp_dir, SkiaGoldProperties(self.options)))
     return self
 
diff --git a/testing/scripts/skia_gold_infra/finch_skia_gold_session_manager.py b/testing/scripts/skia_gold_infra/finch_skia_gold_session_manager.py
deleted file mode 100644
index f9b15a7..0000000
--- a/testing/scripts/skia_gold_infra/finch_skia_gold_session_manager.py
+++ /dev/null
@@ -1,19 +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.
-"""Finch impl of skia_gold_session_manager.py."""
-
-import os
-import sys
-
-THIS_DIR = os.path.abspath(os.path.dirname(__file__))
-CHROMIUM_SRC_DIR = os.path.realpath(os.path.join(THIS_DIR, '..', '..', '..'))
-sys.path.insert(0, os.path.join(CHROMIUM_SRC_DIR, 'build'))
-from skia_gold_common import output_managerless_skia_gold_session
-from skia_gold_common import skia_gold_session_manager as sgsm
-
-
-class FinchSkiaGoldSessionManager(sgsm.SkiaGoldSessionManager):
-  @staticmethod
-  def GetSessionClass():
-    return output_managerless_skia_gold_session.OutputManagerlessSkiaGoldSession
diff --git a/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py b/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py
index bd172c8..5e96a89 100644
--- a/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py
+++ b/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py
@@ -4,9 +4,9 @@
 
 import logging
 import sys
-from .finch_skia_gold_session_manager import FinchSkiaGoldSessionManager
 
 from skia_gold_common.skia_gold_properties import SkiaGoldProperties
+from skia_gold_common.skia_gold_session_manager import SkiaGoldSessionManager
 
 # This is the corpus used by skia gold to identify the data set.
 # We are not using the same corpus as the rest of the skia gold chromium tests.
@@ -17,7 +17,7 @@
 class FinchSkiaGoldUtil:
   def __init__(self, temp_dir, args):
     self._skia_gold_properties = SkiaGoldProperties(args)
-    self._skia_gold_session_manager = FinchSkiaGoldSessionManager(
+    self._skia_gold_session_manager = SkiaGoldSessionManager(
         temp_dir, self._skia_gold_properties)
     self._skia_gold_session = self._GetSkiaGoldSession()
     self._retry_without_patch = False
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index d720da36..f47a05f 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -14,6 +14,28 @@
             ]
         }
     ],
+    "AccessibilityUnserializeOptimizations": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "chromeos_lacros",
+                "fuchsia",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AccessibilityUnserializeOptimizations"
+                    ]
+                }
+            ]
+        }
+    ],
     "AddPageLoadTokenToClientSafeBrowsingReport": [
         {
             "platforms": [
@@ -274,6 +296,23 @@
             ]
         }
     ],
+    "AndroidOmniboxPerf": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "All_Enabled",
+                    "enable_features": [
+                        "OmniboxCacheSuggestionResources",
+                        "OmniboxIgnoreIntermediateResults",
+                        "OmniboxWarmRecycledViewPool"
+                    ]
+                }
+            ]
+        }
+    ],
     "AndroidOmniboxUxRevampPhase2": [
         {
             "platforms": [
@@ -1731,26 +1770,6 @@
             ]
         }
     ],
-    "BackForwardCacheEnabledForIndexedDBConnection": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_20230331",
-                    "enable_features": [
-                        "AllowPageWithIDBConnectionInBFCache",
-                        "AllowPageWithIDBTransactionInBFCache"
-                    ]
-                }
-            ]
-        }
-    ],
     "BackForwardCacheMemoryControls": [
         {
             "platforms": [
@@ -5000,6 +5019,22 @@
             ]
         }
     ],
+    "EnablePreferencesAccountStorage": [
+        {
+            "platforms": [
+                "android",
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "EnablePreferencesAccountStorage"
+                    ]
+                }
+            ]
+        }
+    ],
     "EnableShoppingListDesktop": [
         {
             "platforms": [
@@ -6378,28 +6413,6 @@
             ]
         }
     ],
-    "HatsGeneralCamera": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledWithGooglers_20230317",
-                    "params": {
-                        "enabled_for_googlers": "true",
-                        "prob": "0.01",
-                        "survey_cycle_length": "90",
-                        "survey_start_date_ms": "1677715200000",
-                        "trigger_id": "vpqxGW9RC0jBnuKU19R0S2ceNB9H"
-                    },
-                    "enable_features": [
-                        "HappinessTrackingGeneralCamera"
-                    ]
-                }
-            ]
-        }
-    ],
     "HatsOnboardingExperience": [
         {
             "platforms": [
@@ -8940,7 +8953,7 @@
             ]
         }
     ],
-    "OmniboxOnFocusMVCarouseliOS": [
+    "OmniboxOnFocusMVCarouseliOSV3": [
         {
             "platforms": [
                 "ios"
@@ -8951,7 +8964,6 @@
                     "enable_features": [
                         "EnableSuggestionsScrollingOnIPad",
                         "OmniboxFocusTriggersContextualWebZeroSuggest",
-                        "OmniboxFocusTriggersSRPZeroSuggest",
                         "OmniboxMostVisitedTiles"
                     ]
                 }
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 4d3f1061..69aee5d 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -809,10 +809,6 @@
              "ResamplingInputEvents",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kInputTargetClientHighPriority,
-             "InputTargetClientHighPriority",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kResamplingScrollEvents,
              "ResamplingScrollEvents",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 891d3c6a..72524358 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -315,10 +315,6 @@
 // Enables resampling input events on main thread.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kResamplingInputEvents);
 
-// Elevates the InputTargetClient mojo interface to input, since its input
-// blocking.
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kInputTargetClientHighPriority);
-
 // Enables passing of mailbox backed Accelerated bitmap images to be passed
 // cross-process as mailbox references instead of serialized bitmaps in
 // shared memory.
diff --git a/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom b/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
index a0f2c38..33e6364 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
@@ -808,6 +808,11 @@
     kScrollStartX = 754,
     kScrollStartY = 755,
     kScrollStart = 756,
+    kScrollStartTargetBlock = 757,
+    kScrollStartTargetInline = 758,
+    kScrollStartTargetX = 759,
+    kScrollStartTargetY = 760,
+    kScrollStartTarget = 761,
     // 1. Add new features above this line (don't change the assigned numbers of
     //    the existing items).
     // 2. Run the src/tools/metrics/histograms/update_use_counter_css.py script
diff --git a/third_party/blink/public/web/web_picture_in_picture_window_options.h b/third_party/blink/public/web/web_picture_in_picture_window_options.h
index a7d5d0b..57ca203 100644
--- a/third_party/blink/public/web/web_picture_in_picture_window_options.h
+++ b/third_party/blink/public/web/web_picture_in_picture_window_options.h
@@ -12,7 +12,6 @@
 struct WebPictureInPictureWindowOptions {
   uint64_t width = 0;
   uint64_t height = 0;
-  double initial_aspect_ratio = 0.0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/binding_security.cc b/third_party/blink/renderer/bindings/core/v8/binding_security.cc
index cef68f7..1f80dfb 100644
--- a/third_party/blink/renderer/bindings/core/v8/binding_security.cc
+++ b/third_party/blink/renderer/bindings/core/v8/binding_security.cc
@@ -239,15 +239,6 @@
     const DOMWindow* target,
     ErrorReportOption reporting_option) {
   DCHECK(target);
-
-  // TODO(https://crbug.com/723057): This is intended to match the legacy
-  // behavior of when access checks revolved around Frame pointers rather than
-  // DOMWindow pointers. This prevents web-visible behavior changes, since the
-  // previous implementation had to follow the back pointer to the Frame, and
-  // would have to early return when it was null.
-  if (!target->GetFrame())
-    return false;
-
   bool can_access = CanAccessWindow(accessing_window, target, reporting_option);
 
   if (!can_access && accessing_window) {
@@ -267,15 +258,6 @@
     const Location* target,
     ErrorReportOption reporting_option) {
   DCHECK(target);
-
-  // TODO(https://crbug.com/723057): This is intended to match the legacy
-  // behavior of when access checks revolved around Frame pointers rather than
-  // DOMWindow pointers. This prevents web-visible behavior changes, since the
-  // previous implementation had to follow the back pointer to the Frame, and
-  // would have to early return when it was null.
-  if (!target->DomWindow()->GetFrame())
-    return false;
-
   bool can_access =
       CanAccessWindow(accessing_window, target->DomWindow(), reporting_option);
 
@@ -400,13 +382,11 @@
   // exception could be a security issue, so just crash.
   CHECK(target);
 
-  // TODO(https://crbug.com/723057): This is intended to match the legacy
-  // behavior of when access checks revolved around Frame pointers rather than
-  // DOMWindow pointers. This prevents web-visible behavior changes, since the
-  // previous implementation had to follow the back pointer to the Frame, and
-  // would have to early return when it was null.
-  if (!target->GetFrame())
+  // wpt/html/cross-origin-opener-policy/resource-popup.https.html expects
+  // that illegally accessing a detached window doesn't throw.
+  if (!target->GetFrame()) {
     return;
+  }
 
   auto* local_dom_window = CurrentDOMWindow(isolate);
   // Determine if the access check failure was because of cross-origin or if the
@@ -429,32 +409,4 @@
                                             cross_document_access));
 }
 
-bool BindingSecurity::ShouldAllowNamedAccessTo(
-    const DOMWindow* accessing_window,
-    const DOMWindow* target_window) {
-  const Frame* accessing_frame = accessing_window->GetFrame();
-  DCHECK(accessing_frame);
-  DCHECK(accessing_frame->GetSecurityContext());
-  const SecurityOrigin* accessing_origin =
-      accessing_frame->GetSecurityContext()->GetSecurityOrigin();
-
-  const Frame* target_frame = target_window->GetFrame();
-  DCHECK(target_frame);
-  DCHECK(target_frame->GetSecurityContext());
-  const SecurityOrigin* target_origin =
-      target_frame->GetSecurityContext()->GetSecurityOrigin();
-  SECURITY_CHECK(!(target_window && target_window->GetFrame()) ||
-                 target_window == target_window->GetFrame()->DomWindow());
-
-  if (!accessing_origin->CanAccess(target_origin))
-    return false;
-
-  // Note that there is no need to call back
-  // FrameLoader::didAccessInitialDocument() because |targetWindow| must be
-  // a child window inside iframe or frame and it doesn't have a URL bar,
-  // so there is no need to worry about URL spoofing.
-
-  return true;
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/binding_security.h b/third_party/blink/renderer/bindings/core/v8/binding_security.h
index 5d1d8e4d..eb2c628 100644
--- a/third_party/blink/renderer/bindings/core/v8/binding_security.h
+++ b/third_party/blink/renderer/bindings/core/v8/binding_security.h
@@ -100,6 +100,11 @@
       v8::MaybeLocal<v8::Context> target_context,
       ErrorReportOption);
 
+  static void FailedAccessCheckFor(v8::Isolate*,
+                                   const WrapperTypeInfo*,
+                                   v8::Local<v8::Object> holder);
+
+ private:
   // Checks if a wrapper creation of the given wrapper type associated with
   // |creation_context| is allowed in |accessing_context|.
   static bool ShouldAllowWrapperCreationOrThrowException(
@@ -114,25 +119,6 @@
       v8::MaybeLocal<v8::Context> creation_context,
       const WrapperTypeInfo* wrapper_type_info,
       v8::Local<v8::Value> cross_context_exception);
-
-  static void FailedAccessCheckFor(v8::Isolate*,
-                                   const WrapperTypeInfo*,
-                                   v8::Local<v8::Object> holder);
-
- private:
-  // Returns true if |accessingWindow| is allowed named access to |targetWindow|
-  // because they're the same origin.  Note that named access should be allowed
-  // even if they're cross origin as long as the browsing context name matches
-  // the browsing context container's name.
-  //
-  // Unlike shouldAllowAccessTo, this function returns true even when
-  // |accessingWindow| or |targetWindow| is a RemoteDOMWindow, but remember that
-  // only limited operations are allowed on a RemoteDOMWindow.
-  //
-  // This function should be only used from V8Window::NamedPropertyGetterCustom.
-  friend class V8Window;
-  static bool ShouldAllowNamedAccessTo(const DOMWindow* accessing_window,
-                                       const DOMWindow* target_window);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/custom/v8_window_custom.cc b/third_party/blink/renderer/bindings/core/v8/custom/v8_window_custom.cc
index 230101a..c56b4c8 100644
--- a/third_party/blink/renderer/bindings/core/v8/custom/v8_window_custom.cc
+++ b/third_party/blink/renderer/bindings/core/v8/custom/v8_window_custom.cc
@@ -215,7 +215,8 @@
     // active document's origin is not same origin with activeDocument's origin
     // and whose browsing context name does not match the name of its browsing
     // context container's name content attribute value.
-    if (BindingSecurity::ShouldAllowNamedAccessTo(window, child->DomWindow()) ||
+    if (frame->GetSecurityContext()->GetSecurityOrigin()->CanAccess(
+            child->GetSecurityContext()->GetSecurityOrigin()) ||
         name == child->Owner()->BrowsingContextContainerName()) {
       bindings::V8SetReturnValue(
           info, child->DomWindow(), window,
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index 491fe7c..2646d3bd 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -146,8 +146,8 @@
 
 // NOTE: when editing this, please also edit the error messages we throw when
 // the size is exceeded (see uses of the constant), which use the human-friendly
-// "4KB" text.
-const size_t kWasmWireBytesLimit = 1 << 12;
+// "8MB" text.
+const size_t kWasmWireBytesLimit = 1 << 23;
 
 }  // namespace
 
@@ -538,10 +538,16 @@
   isolate->ThrowException(NewRangeException(isolate, message));
 }
 
+BASE_FEATURE(kWebAssemblyUnlimitedSyncCompilation,
+             "WebAssemblyUnlimitedSyncCompilation",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 bool WasmModuleOverride(const v8::FunctionCallbackInfo<v8::Value>& args) {
   // Return false if we want the base behavior to proceed.
-  if (!WTF::IsMainThread() || args.Length() < 1)
+  if (!WTF::IsMainThread() || args.Length() < 1 ||
+      base::FeatureList::IsEnabled(kWebAssemblyUnlimitedSyncCompilation)) {
     return false;
+  }
   v8::Local<v8::Value> source = args[0];
   if ((source->IsArrayBuffer() &&
        v8::Local<v8::ArrayBuffer>::Cast(source)->ByteLength() >
@@ -549,10 +555,12 @@
       (source->IsArrayBufferView() &&
        v8::Local<v8::ArrayBufferView>::Cast(source)->ByteLength() >
            kWasmWireBytesLimit)) {
-    ThrowRangeException(args.GetIsolate(),
-                        "WebAssembly.Compile is disallowed on the main thread, "
-                        "if the buffer size is larger than 4KB. Use "
-                        "WebAssembly.compile, or compile on a worker thread.");
+    ThrowRangeException(
+        args.GetIsolate(),
+        "WebAssembly.Compile is disallowed on the main thread, "
+        "if the buffer size is larger than 8MB. Use "
+        "WebAssembly.compile, compile on a worker thread, or use the flag "
+        "`--enable-features=WebAssemblyUnlimitedSyncCompilation`.");
     // Return true because we injected new behavior and we do not
     // want the default behavior.
     return true;
@@ -562,8 +570,10 @@
 
 bool WasmInstanceOverride(const v8::FunctionCallbackInfo<v8::Value>& args) {
   // Return false if we want the base behavior to proceed.
-  if (!WTF::IsMainThread() || args.Length() < 1)
+  if (!WTF::IsMainThread() || args.Length() < 1 ||
+      base::FeatureList::IsEnabled(kWebAssemblyUnlimitedSyncCompilation)) {
     return false;
+  }
   v8::Local<v8::Value> source = args[0];
   if (!source->IsWasmModuleObject())
     return false;
@@ -574,8 +584,9 @@
     ThrowRangeException(
         args.GetIsolate(),
         "WebAssembly.Instance is disallowed on the main thread, "
-        "if the buffer size is larger than 4KB. Use "
-        "WebAssembly.instantiate.");
+        "if the buffer size is larger than 8MB. Use "
+        "WebAssembly.instantiate, or use the flag "
+        "`--enable-features=WebAssemblyUnlimitedSyncCompilation`.");
     return true;
   }
   return false;
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc b/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
index e563dc9..d8120da 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
@@ -728,10 +728,10 @@
     DCHECK(!ScriptForbiddenScope::WillBeScriptForbidden());
   }
 
-  CHECK(!window || !window->GetFrame() ||
-        BindingSecurity::ShouldAllowAccessTo(
-            ToLocalDOMWindow(function->GetCreationContextChecked()), window,
-            BindingSecurity::ErrorReportOption::kDoNotReport));
+  DCHECK(!window || !window->GetFrame() ||
+         BindingSecurity::ShouldAllowAccessTo(
+             ToLocalDOMWindow(function->GetCreationContextChecked()), window,
+             BindingSecurity::ErrorReportOption::kDoNotReport));
   v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
   v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
                                        v8::MicrotasksScope::kRunMicrotasks);
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 13623e2..d520b17 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -827,6 +827,16 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_presentation_source.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_pressure_observer_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_pressure_observer_options.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_encrypted_match_key.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_encrypted_match_key.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_helper_info.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_helper_info.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_helper_share.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_helper_share.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_network.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_network.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_options.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_creation_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_creation_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_descriptor.cc",
@@ -1470,6 +1480,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_pressure_source.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_pressure_state.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_pressure_state.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_event.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution_event.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_type.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_type.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_push_encryption_key_name.cc",
@@ -2351,6 +2363,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_pressure_record.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_aggregation.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_aggregation.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_private_attribution.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_push_event.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index f323049..131e51c6 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -632,6 +632,8 @@
           "//third_party/blink/renderer/modules/presentation/presentation_receiver.idl",
           "//third_party/blink/renderer/modules/presentation/presentation_request.idl",
           "//third_party/blink/renderer/modules/presentation/presentation_source.idl",
+          "//third_party/blink/renderer/modules/private_attribution/private_attribution.idl",
+          "//third_party/blink/renderer/modules/private_attribution/window_private_attribution.idl",
           "//third_party/blink/renderer/modules/push_messaging/push_event.idl",
           "//third_party/blink/renderer/modules/push_messaging/push_event_init.idl",
           "//third_party/blink/renderer/modules/push_messaging/push_manager.idl",
diff --git a/third_party/blink/renderer/core/DEPS b/third_party/blink/renderer/core/DEPS
index e136775b..f4b3b1c 100644
--- a/third_party/blink/renderer/core/DEPS
+++ b/third_party/blink/renderer/core/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+    "+base/apple/bridging.h",
     "+base/atomic_sequence_num.h",
     "+base/barrier_closure.h",
     "+base/cancelable_callback.h",
@@ -6,7 +7,6 @@
     "+base/files/file.h",
     "+base/files/file_path.h",
     "+base/guid.h",
-    "+base/mac/bridging.h",
     "+base/mac/foundation_util.h",
     "+base/mac/scoped_cftyperef.h",
     "+base/memory/scoped_refptr.h",
diff --git a/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc
index b53405f..48ad44b 100644
--- a/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/core/style/basic_shapes.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
+#include "third_party/blink/renderer/core/style/shape_offset_path_operation.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -34,6 +35,23 @@
       if (style.ShapeOutside()->CssBox() != CSSBoxType::kMissing)
         return nullptr;
       return style.ShapeOutside()->Shape();
+    case CSSPropertyID::kOffsetPath: {
+      auto* offset_path_operation =
+          DynamicTo<ShapeOffsetPathOperation>(style.OffsetPath());
+      if (!offset_path_operation) {
+        return nullptr;
+      }
+      const auto& shape = offset_path_operation->GetBasicShape();
+
+      // Path and Ray shapes are handled by PathInterpolationType and
+      // RayInterpolationType.
+      if (shape.GetType() == BasicShape::kStylePathType ||
+          shape.GetType() == BasicShape::kStyleRayType) {
+        return nullptr;
+      }
+
+      return &shape;
+    }
     case CSSPropertyID::kClipPath: {
       auto* clip_path_operation =
           DynamicTo<ShapeClipPathOperation>(style.ClipPath());
@@ -136,8 +154,12 @@
     return basic_shape_interpolation_functions::MaybeConvertCSSValue(value);
 
   const auto& list = To<CSSValueList>(value);
-  if (list.length() != 1)
+  // Path and Ray shapes are handled by PathInterpolationType and
+  // RayInterpolationType.
+  if (!list.First().IsBasicShapeValue() || list.First().IsRayValue() ||
+      list.First().IsPathValue()) {
     return nullptr;
+  }
   return basic_shape_interpolation_functions::MaybeConvertCSSValue(
       list.Item(0));
 }
@@ -189,6 +211,11 @@
       state.StyleBuilder().SetShapeOutside(MakeGarbageCollected<ShapeValue>(
           std::move(shape), CSSBoxType::kMissing));
       break;
+    case CSSPropertyID::kOffsetPath:
+      // TODO(sakhapov): handle coord box.
+      state.StyleBuilder().SetOffsetPath(ShapeOffsetPathOperation::Create(
+          std::move(shape), CoordBox::kBorderBox));
+      break;
     case CSSPropertyID::kClipPath:
       state.StyleBuilder().SetClipPath(
           ShapeClipPathOperation::Create(std::move(shape)));
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc b/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
index 48d04c2..ee5e690 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
+++ b/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
@@ -266,6 +266,8 @@
         break;
       case CSSPropertyID::kOffsetPath:
         applicable_types->push_back(
+            std::make_unique<CSSBasicShapeInterpolationType>(used_property));
+        applicable_types->push_back(
             std::make_unique<CSSRayInterpolationType>(used_property));
         [[fallthrough]];
       case CSSPropertyID::kD:
diff --git a/third_party/blink/renderer/core/css/css_image_set_value.cc b/third_party/blink/renderer/core/css/css_image_set_value.cc
index 0cf076d..d97eaf7 100644
--- a/third_party/blink/renderer/core/css/css_image_set_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_set_value.cc
@@ -81,6 +81,13 @@
                          return left->ComputedResolution() <
                                 right->ComputedResolution();
                        });
+      auto* last = std::unique(options_.begin(), options_.end(),
+                               [](const CSSImageSetOptionValue* left,
+                                  const CSSImageSetOptionValue* right) {
+                                 return left->ComputedResolution() ==
+                                        right->ComputedResolution();
+                               });
+      options_.erase(last, options_.end());
     }
   }
 
diff --git a/third_party/blink/renderer/core/css/css_primitive_value_mappings.h b/third_party/blink/renderer/core/css/css_primitive_value_mappings.h
index 439b608..cdb1425 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value_mappings.h
+++ b/third_party/blink/renderer/core/css/css_primitive_value_mappings.h
@@ -1941,6 +1941,32 @@
   }
 }
 
+template <>
+inline CSSIdentifierValue::CSSIdentifierValue(EScrollStartTarget target)
+    : CSSValue(kIdentifierClass) {
+  switch (target) {
+    case EScrollStartTarget::kNone:
+      value_id_ = CSSValueID::kNone;
+      break;
+    case EScrollStartTarget::kAuto:
+      value_id_ = CSSValueID::kAuto;
+      break;
+  };
+}
+
+template <>
+inline EScrollStartTarget CSSIdentifierValue::ConvertTo() const {
+  switch (GetValueID()) {
+    case CSSValueID::kNone:
+      return EScrollStartTarget::kNone;
+    case CSSValueID::kAuto:
+      return EScrollStartTarget::kAuto;
+    default:
+      NOTREACHED();
+      return EScrollStartTarget::kNone;
+  };
+}
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_PRIMITIVE_VALUE_MAPPINGS_H_
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 6fb4cfd..28919b9 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -444,7 +444,8 @@
                        "border-style", "border-width", "contain-intrinsic-size",
                        "inset", "margin", "max-size", "min-size", "overflow",
                        "padding", "scroll-margin", "scroll-padding",
-                       "scroll-start", "size", "visited-border-color"],
+                       "scroll-start", "scroll-start-target", "size",
+                       "visited-border-color"],
       },
       // The name of the mapping function used to convert between equivalent
       // logical and physical properties within the same group. Corresponds to
@@ -4494,6 +4495,64 @@
       runtime_flag: "CSSScrollSnap2",
     },
     {
+      name: "scroll-start-target-block",
+      property_methods: ["ParseSingleValue"],
+      field_group: "*->target",
+      field_template: "keyword",
+      default_value: "none",
+      type_name: "EScrollStartTarget",
+      logical_property_group: {
+        name: "scroll-start-target",
+        resolver: "block",
+      },
+      keywords: ["none", "auto"],
+      typedom_types: ["Keyword"],
+      runtime_flag: "CSSScrollSnap2",
+    },
+    {
+      name: "scroll-start-target-inline",
+      property_methods: ["ParseSingleValue"],
+      field_group: "*->target",
+      field_template: "keyword",
+      default_value: "none",
+      type_name: "EScrollStartTarget",
+      logical_property_group: {
+        name: "scroll-start-target",
+        resolver: "inline",
+      },
+      keywords: ["none", "auto"],
+      typedom_types: ["Keyword"],
+      runtime_flag: "CSSScrollSnap2",
+    },
+    {
+      name: "scroll-start-target-x",
+      property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
+      field_group: "*->target",
+      field_template: "keyword",
+      type_name: "EScrollStartTarget",
+      default_value: "none",
+      logical_property_group: {
+        name: "scroll-start-target",
+        resolver: "horizontal"
+      },
+      keywords: ["none", "auto"],
+      runtime_flag: "CSSScrollSnap2",
+    },
+    {
+      name: "scroll-start-target-y",
+      property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
+      field_group: "*->target",
+      field_template: "keyword",
+      type_name: "EScrollStartTarget",
+      default_value: "none",
+      logical_property_group: {
+        name: "scroll-start-target",
+        resolver: "vertical"
+      },
+      keywords: ["none", "auto"],
+      runtime_flag: "CSSScrollSnap2",
+    },
+    {
       name: "scroll-timeline-attachment",
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal", "InitialValue"],
       field_group: "*->timeline",
@@ -7280,6 +7339,12 @@
       runtime_flag: "CSSScrollSnap2",
     },
     {
+      name: "scroll-start-target",
+      longhands: ["scroll-start-target-block", "scroll-start-target-inline"],
+      property_methods: ["ParseShorthand", "CSSValueFromComputedStyleInternal"],
+      runtime_flag: "CSSScrollSnap2",
+    },
+    {
       name: "scroll-timeline",
       longhands: ["scroll-timeline-name", "scroll-timeline-axis", "scroll-timeline-attachment"],
       property_methods: ["ParseShorthand", "CSSValueFromComputedStyleInternal"],
diff --git a/third_party/blink/renderer/core/css/css_property_equality.cc b/third_party/blink/renderer/core/css/css_property_equality.cc
index f6ccc81..d86dc431 100644
--- a/third_party/blink/renderer/core/css/css_property_equality.cc
+++ b/third_party/blink/renderer/core/css/css_property_equality.cc
@@ -625,6 +625,10 @@
       return a.ScrollStartX() == b.ScrollStartX();
     case CSSPropertyID::kScrollStartY:
       return a.ScrollStartY() == b.ScrollStartY();
+    case CSSPropertyID::kScrollStartTargetX:
+      return a.ScrollStartTargetX() == b.ScrollStartTargetX();
+    case CSSPropertyID::kScrollStartTargetY:
+      return a.ScrollStartTargetY() == b.ScrollStartTargetY();
     case CSSPropertyID::kShapeImageThreshold:
       return a.ShapeImageThreshold() == b.ShapeImageThreshold();
     case CSSPropertyID::kShapeMargin:
@@ -1095,6 +1099,8 @@
     case CSSPropertyID::kScrollPaddingInlineStart:
     case CSSPropertyID::kScrollStartBlock:
     case CSSPropertyID::kScrollStartInline:
+    case CSSPropertyID::kScrollStartTargetBlock:
+    case CSSPropertyID::kScrollStartTargetInline:
     case CSSPropertyID::kInlineSize:
     case CSSPropertyID::kInsetBlock:
     case CSSPropertyID::kInsetInline:
@@ -1192,6 +1198,7 @@
     case CSSPropertyID::kScrollMargin:
     case CSSPropertyID::kScrollPadding:
     case CSSPropertyID::kScrollStart:
+    case CSSPropertyID::kScrollStartTarget:
     case CSSPropertyID::kScrollTimeline:
     case CSSPropertyID::kTextDecoration:
     case CSSPropertyID::kTextEmphasis:
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.cc b/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.cc
index f49f939..5d14f19 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.cc
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.cc
@@ -38,11 +38,12 @@
   DrawInternal(canvas, flags, dest_rect, src_rect, draw_options, image_);
 }
 
-void PaintWorkletDeferredImage::DrawTile(GraphicsContext& context,
+void PaintWorkletDeferredImage::DrawTile(cc::PaintCanvas* canvas,
                                          const gfx::RectF& src_rect,
                                          const ImageDrawOptions& draw_options) {
-  DrawInternal(context.Canvas(), context.FillFlags(), gfx::RectF(), src_rect,
-               draw_options, image_);
+  cc::PaintFlags flags;
+  flags.setAntiAlias(true);
+  DrawInternal(canvas, flags, gfx::RectF(), src_rect, draw_options, image_);
 }
 
 sk_sp<PaintShader> PaintWorkletDeferredImage::CreateShader(
@@ -51,10 +52,8 @@
     const gfx::RectF& src_rect,
     const ImageDrawOptions&) {
   SkRect tile = gfx::RectFToSkRect(tile_rect);
-  sk_sp<PaintShader> shader = PaintShader::MakeImage(
-      image_, SkTileMode::kRepeat, SkTileMode::kRepeat, pattern_matrix, &tile);
-
-  return shader;
+  return PaintShader::MakeImage(image_, SkTileMode::kRepeat,
+                                SkTileMode::kRepeat, pattern_matrix, &tile);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.h b/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.h
index 7ddc5721..39ac4ef 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.h
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.h
@@ -36,7 +36,7 @@
             const gfx::RectF& dest_rect,
             const gfx::RectF& src_rect,
             const ImageDrawOptions&) override;
-  void DrawTile(GraphicsContext&,
+  void DrawTile(cc::PaintCanvas*,
                 const gfx::RectF&,
                 const ImageDrawOptions&) override;
   sk_sp<cc::PaintShader> CreateShader(const gfx::RectF& tile_rect,
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.h b/third_party/blink/renderer/core/css/element_rule_collector.h
index 9870eea5..3f346584 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.h
+++ b/third_party/blink/renderer/core/css/element_rule_collector.h
@@ -24,6 +24,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_ELEMENT_RULE_COLLECTOR_H_
 
 #include "base/auto_reset.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/container_selector.h"
diff --git a/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc b/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
index 18dfa364..f2f1001 100644
--- a/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
@@ -716,10 +716,6 @@
   TestImageSetParsing("image-set(url(foo) 0x)", "image-set(url(\"foo\") 0x)");
 }
 
-TEST(CSSPropertyParserTest, ImageSetNegativeResolution) {
-  TestImageSetParsing("image-set(url(foo) -1x)", "image-set(url(\"foo\") -1x)");
-}
-
 TEST(CSSPropertyParserTest, ImageSetCalcResolutionUnitX) {
   TestImageSetParsing("image-set(url(foo) calc(1x))",
                       "image-set(url(\"foo\") calc(1dppx))");
@@ -885,6 +881,10 @@
   TestImageSetParsingFailure("image-set(1x)");
 }
 
+TEST(CSSPropertyParserTest, ImageSetNegativeResolution) {
+  TestImageSetParsingFailure("image-set(url(foo) -1x)");
+}
+
 TEST(CSSPropertyParserTest, ImageSetOnlyOneGradientColor) {
   TestImageSetParsingFailure("image-set(linear-gradient(red) 1x)");
 }
diff --git a/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.cc b/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.cc
index cd28a3a..dc78522 100644
--- a/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.cc
+++ b/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.cc
@@ -251,6 +251,20 @@
   return PhysicalMapping<2>(kProperties);
 }
 
+LogicalMapping<2> CSSDirectionAwareResolver::LogicalScrollStartTargetMapping() {
+  static const CSSProperty* kProperties[] = {
+      &GetCSSPropertyScrollStartTargetBlock(),
+      &GetCSSPropertyScrollStartTargetInline()};
+  return LogicalMapping<2>(kProperties);
+}
+
+PhysicalMapping<2>
+CSSDirectionAwareResolver::PhysicalScrollStartTargetMapping() {
+  static const CSSProperty* kProperties[] = {
+      &GetCSSPropertyScrollStartTargetX(), &GetCSSPropertyScrollStartTargetY()};
+  return PhysicalMapping<2>(kProperties);
+}
+
 LogicalMapping<2> CSSDirectionAwareResolver::LogicalSizeMapping() {
   static const CSSProperty* kProperties[] = {&GetCSSPropertyBlockSize(),
                                              &GetCSSPropertyInlineSize()};
diff --git a/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.h b/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.h
index 8bc7927..6def92d 100644
--- a/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.h
+++ b/third_party/blink/renderer/core/css/properties/css_direction_aware_resolver.h
@@ -69,6 +69,7 @@
   static LogicalMapping<4> LogicalScrollMarginMapping();
   static LogicalMapping<4> LogicalScrollPaddingMapping();
   static LogicalMapping<2> LogicalScrollStartMapping();
+  static LogicalMapping<2> LogicalScrollStartTargetMapping();
   static LogicalMapping<2> LogicalSizeMapping();
   static LogicalMapping<4> LogicalVisitedBorderColorMapping();
 
@@ -88,6 +89,7 @@
   static PhysicalMapping<4> PhysicalScrollMarginMapping();
   static PhysicalMapping<4> PhysicalScrollPaddingMapping();
   static PhysicalMapping<2> PhysicalScrollStartMapping();
+  static PhysicalMapping<2> PhysicalScrollStartTargetMapping();
   static PhysicalMapping<2> PhysicalSizeMapping();
   static PhysicalMapping<4> PhysicalVisitedBorderColorMapping();
 
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 5cc8589b..d798cb6 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -3552,6 +3552,9 @@
   }
 
   CSSPrimitiveValue* resolution = ConsumeResolution(range, context);
+  if (resolution && resolution->GetDoubleValue() < 0.0) {
+    return nullptr;
+  }
 
   if (!type) {
     type = ConsumeImageSetType(range);
@@ -6557,6 +6560,10 @@
                                 CSSPrimitiveValue::ValueRange::kNonNegative);
 }
 
+CSSValue* ConsumeScrollStartTarget(CSSParserTokenRange& range) {
+  return ConsumeIdent<CSSValueID::kAuto, CSSValueID::kNone>(range);
+}
+
 CSSValue* ConsumeOffsetPath(CSSParserTokenRange& range,
                             const CSSParserContext& context) {
   CSSValue* offset_path = nullptr;
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
index 345ce3c..2585a3e8 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -495,6 +495,7 @@
                                 CSSAnchorQueryTypes = kCSSAnchorQueryTypesNone);
 CSSValue* ConsumeScrollPadding(CSSParserTokenRange&, const CSSParserContext&);
 CSSValue* ConsumeScrollStart(CSSParserTokenRange&, const CSSParserContext&);
+CSSValue* ConsumeScrollStartTarget(CSSParserTokenRange&);
 CSSValue* ConsumeOffsetPath(CSSParserTokenRange&, const CSSParserContext&);
 CSSValue* ConsumePathOrNone(CSSParserTokenRange&);
 CSSValue* ConsumeOffsetRotate(CSSParserTokenRange&, const CSSParserContext&);
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index a1595ad..fa551c1 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -7269,6 +7269,48 @@
   return ComputedStyleUtils::ValueForScrollStart(style, style.ScrollStartY());
 }
 
+const CSSValue* ScrollStartTargetBlock::ParseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) const {
+  return css_parsing_utils::ConsumeScrollStartTarget(range);
+}
+
+const CSSValue* ScrollStartTargetInline::ParseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) const {
+  return css_parsing_utils::ConsumeScrollStartTarget(range);
+}
+
+const CSSValue* ScrollStartTargetX::ParseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) const {
+  return css_parsing_utils::ConsumeScrollStartTarget(range);
+}
+
+const CSSValue* ScrollStartTargetX::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const LayoutObject*,
+    bool allow_visited_style) const {
+  return CSSIdentifierValue::Create(style.ScrollStartTargetX());
+}
+
+const CSSValue* ScrollStartTargetY::ParseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) const {
+  return css_parsing_utils::ConsumeScrollStartTarget(range);
+}
+
+const CSSValue* ScrollStartTargetY::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const LayoutObject*,
+    bool allow_visited_style) const {
+  return CSSIdentifierValue::Create(style.ScrollStartTargetY());
+}
+
 const CSSValue* ScrollTimelineAttachment::InitialValue() const {
   CSSValueList* list = CSSValueList::CreateCommaSeparated();
   list->Append(*CSSIdentifierValue::Create(CSSValueID::kLocal));
diff --git a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
index 4064f51..4afca3d 100644
--- a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
@@ -3349,6 +3349,46 @@
   return block_value;
 }
 
+bool ScrollStartTarget::ParseShorthand(
+    bool important,
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext& local_context,
+    HeapVector<CSSPropertyValue, 64>& properties) const {
+  CSSValue* block_value = css_parsing_utils::ConsumeScrollStartTarget(range);
+  if (!block_value) {
+    return false;
+  }
+  CSSValue* inline_value = css_parsing_utils::ConsumeScrollStartTarget(range);
+  if (!inline_value) {
+    inline_value = CSSIdentifierValue::Create(CSSValueID::kNone);
+  }
+  AddProperty(scrollStartTargetShorthand().properties()[0]->PropertyID(),
+              scrollStartTargetShorthand().id(), *block_value, important,
+              css_parsing_utils::IsImplicitProperty::kNotImplicit, properties);
+  AddProperty(scrollStartTargetShorthand().properties()[1]->PropertyID(),
+              scrollStartTargetShorthand().id(), *inline_value, important,
+              css_parsing_utils::IsImplicitProperty::kNotImplicit, properties);
+  return range.AtEnd();
+}
+
+const CSSValue* ScrollStartTarget::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const LayoutObject* layout_object,
+    bool allow_visited_style) const {
+  const CSSValue* block_value =
+      scrollStartTargetShorthand().properties()[0]->CSSValueFromComputedStyle(
+          style, layout_object, allow_visited_style);
+  const CSSValue* inline_value =
+      scrollStartTargetShorthand().properties()[1]->CSSValueFromComputedStyle(
+          style, layout_object, allow_visited_style);
+  if (To<CSSIdentifierValue>(*inline_value).GetValueID() != CSSValueID::kNone) {
+    return MakeGarbageCollected<CSSValuePair>(
+        block_value, inline_value, CSSValuePair::kDropIdenticalValues);
+  }
+  return block_value;
+}
+
 bool ScrollTimeline::ParseShorthand(
     bool important,
     CSSParserTokenRange& range,
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
index 209d90e5..1e9d003 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
@@ -2026,12 +2026,10 @@
 TEST_F(StyleCascadeTest, SubstituteAnimationTaintedInAnimationDelay) {
   TestCascade cascade(GetDocument());
   cascade.Add(AnimationTaintedSet("--x", "1s"));
-  cascade.Add("animation-delay-start", "var(--x)");
-  cascade.Add("animation-delay-end", "var(--x)");
+  cascade.Add("animation-delay", "var(--x)");
   cascade.Apply();
   EXPECT_EQ("1s", cascade.ComputedValue("--x"));
-  EXPECT_EQ("0s", cascade.ComputedValue("animation-delay-start"));
-  EXPECT_EQ("0s", cascade.ComputedValue("animation-delay-end"));
+  EXPECT_EQ("0s", cascade.ComputedValue("animation-delay"));
 }
 
 TEST_F(StyleCascadeTest, SubstituteAnimationTaintedInAnimationProperty) {
@@ -3630,7 +3628,7 @@
   cascade.Add("animation-name: test");
   cascade.Add("animation-timing-function: linear");
   cascade.Add("animation-duration: 10s");
-  cascade.Add("animation-delay-start: -5s");
+  cascade.Add("animation-delay: -5s");
   cascade.Apply();
 
   cascade.AddInterpolations();
@@ -3648,7 +3646,7 @@
   EXPECT_EQ("test", CssTextAt(map, "animation-name"));
   EXPECT_EQ("linear", CssTextAt(map, "animation-timing-function"));
   EXPECT_EQ("10s", CssTextAt(map, "animation-duration"));
-  EXPECT_EQ("-5s", CssTextAt(map, "animation-delay-start"));
+  EXPECT_EQ("-5s", CssTextAt(map, "animation-delay"));
 }
 
 TEST_F(StyleCascadeTest, RevertOrigin) {
diff --git a/third_party/blink/renderer/core/css/rule_set.h b/third_party/blink/renderer/core/css/rule_set.h
index 9731c1e..67e1004 100644
--- a/third_party/blink/renderer/core/css/rule_set.h
+++ b/third_party/blink/renderer/core/css/rule_set.h
@@ -23,6 +23,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_SET_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_SET_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/substring_set_matcher/substring_set_matcher.h"
 #include "base/types/pass_key.h"
 #include "third_party/blink/renderer/core/core_export.h"
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.cc b/third_party/blink/renderer/core/css/style_property_serializer.cc
index 5d18453..95962f2d 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.cc
+++ b/third_party/blink/renderer/core/css/style_property_serializer.cc
@@ -697,6 +697,8 @@
       return String();
     case CSSPropertyID::kScrollStart:
       return ScrollStartValue();
+    case CSSPropertyID::kScrollStartTarget:
+      return ScrollStartTargetValue();
     default:
       NOTREACHED()
           << "Shorthand property "
@@ -2262,4 +2264,29 @@
   return list->CssText();
 }
 
+String StylePropertySerializer::ScrollStartTargetValue() const {
+  CHECK_EQ(scrollStartTargetShorthand().length(), 2u);
+  CHECK_EQ(scrollStartTargetShorthand().properties()[0],
+           &GetCSSPropertyScrollStartTargetBlock());
+  CHECK_EQ(scrollStartTargetShorthand().properties()[1],
+           &GetCSSPropertyScrollStartTargetInline());
+
+  CSSValueList* list = CSSValueList::CreateSpaceSeparated();
+  const CSSValue* block_value =
+      property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartTargetBlock());
+  const CSSValue* inline_value = property_set_.GetPropertyCSSValue(
+      GetCSSPropertyScrollStartTargetInline());
+
+  DCHECK(block_value);
+  DCHECK(inline_value);
+
+  list->Append(*block_value);
+
+  if (To<CSSIdentifierValue>(*inline_value).GetValueID() != CSSValueID::kNone) {
+    list->Append(*inline_value);
+  }
+
+  return list->CssText();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.h b/third_party/blink/renderer/core/css/style_property_serializer.h
index df05c0c..3a12f55 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.h
+++ b/third_party/blink/renderer/core/css/style_property_serializer.h
@@ -84,6 +84,7 @@
   String ContainIntrinsicSizeValue() const;
   String WhiteSpaceValue() const;
   String ScrollStartValue() const;
+  String ScrollStartTargetValue() const;
   String GetPropertyText(const CSSPropertyName&,
                          const String& value,
                          bool is_important,
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index 98e76fb..458792f 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -793,9 +793,7 @@
   DCHECK(ConnectedToView());
   if (auto* layout_object = element_->GetLayoutObject()) {
     layout_object->PaintingLayer()->SetNeedsRepaint();
-    document_->View()->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kDisplayLockContextNeedsPaintArtifactCompositorUpdate);
+    document_->View()->SetPaintArtifactCompositorNeedsUpdate();
     return true;
   }
   return false;
diff --git a/third_party/blink/renderer/core/editing/caret_display_item_client.cc b/third_party/blink/renderer/core/editing/caret_display_item_client.cc
index ddf1dc0..43e625c 100644
--- a/third_party/blink/renderer/core/editing/caret_display_item_client.cc
+++ b/third_party/blink/renderer/core/editing/caret_display_item_client.cc
@@ -164,8 +164,7 @@
     if (new_layout_block) {
       needs_paint_invalidation_ = true;
       // The caret property tree space may have changed.
-      layout_block_->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(
-          PaintArtifactCompositorUpdateReason::kFrameCaretPaint);
+      layout_block_->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
     }
   }
 
@@ -179,8 +178,7 @@
       rect_and_block.box_fragment;
   if (new_box_fragment != box_fragment_) {
     // The caret property tree space may have changed.
-    layout_block_->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::kFrameCaretPaint);
+    layout_block_->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
 
     if (new_box_fragment)
       needs_paint_invalidation_ = true;
diff --git a/third_party/blink/renderer/core/editing/frame_caret.cc b/third_party/blink/renderer/core/editing/frame_caret.cc
index c8ec018..919dff9 100644
--- a/third_party/blink/renderer/core/editing/frame_caret.cc
+++ b/third_party/blink/renderer/core/editing/frame_caret.cc
@@ -220,8 +220,7 @@
     }
   }
   // Fallback to full update if direct update is not available.
-  frame_->View()->SetPaintArtifactCompositorNeedsUpdate(
-      PaintArtifactCompositorUpdateReason::kFrameCaretSetVisible);
+  frame_->View()->SetPaintArtifactCompositorNeedsUpdate();
 }
 
 void FrameCaret::PaintCaret(GraphicsContext& context,
@@ -235,8 +234,7 @@
       PaintPropertyChangeType::kUnchanged) {
     // Needs full PaintArtifactCompositor update if the parent or the local
     // transform space changed.
-    frame_->View()->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::kFrameCaretPaint);
+    frame_->View()->SetPaintArtifactCompositorNeedsUpdate();
   }
   ScopedPaintChunkProperties scoped_properties(context.GetPaintController(),
                                                *effect_, *display_item_client_,
diff --git a/third_party/blink/renderer/core/editing/substring_util.mm b/third_party/blink/renderer/core/editing/substring_util.mm
index 60f1f30..bb5f38f 100644
--- a/third_party/blink/renderer/core/editing/substring_util.mm
+++ b/third_party/blink/renderer/core/editing/substring_util.mm
@@ -33,7 +33,7 @@
 
 #import <Cocoa/Cocoa.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -117,7 +117,7 @@
         @(primaryFont->GetFontMetrics().Descent() * page_scale_factor);
 
     NSFont* original_font =
-        base::mac::CFToNSPtrCast(font_platform_data.CtFont());
+        base::apple::CFToNSPtrCast(font_platform_data.CtFont());
     const CGFloat desired_size =
         font_platform_data.size() * page_scale_factor / device_scale_factor;
 
@@ -215,7 +215,7 @@
   NSAttributedString* string = AttributedSubstringFromRange(frame, word_range);
   baseline_point = GetBaselinePoint(frame->View(), word_range, string);
   return base::ScopedCFTypeRef<CFAttributedStringRef>(
-      base::mac::NSToCFOwnershipCast(string));
+      base::apple::NSToCFOwnershipCast(string));
 }
 
 base::ScopedCFTypeRef<CFAttributedStringRef>
@@ -239,7 +239,7 @@
       AttributedSubstringFromRange(frame, ephemeral_range);
   baseline_point = GetBaselinePoint(frame->View(), ephemeral_range, string);
   return base::ScopedCFTypeRef<CFAttributedStringRef>(
-      base::mac::NSToCFOwnershipCast(string));
+      base::apple::NSToCFOwnershipCast(string));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h b/third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h
index f3fb097..a8ce3d2 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_HANDLER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_HANDLER_H_
 
+#include "base/gtest_prod_util.h"
 #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
 #include "third_party/blink/public/mojom/link_to_text/link_to_text.mojom-blink.h"
 #include "third_party/blink/renderer/core/core_export.h"
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_selector_generator.h b/third_party/blink/renderer/core/fragment_directive/text_fragment_selector_generator.h
index fe8f946e..51e93931 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_selector_generator.h
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_selector_generator.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_SELECTOR_GENERATOR_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_SELECTOR_GENERATOR_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/time/time.h"
 #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 833fd0b..fa70d68 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1203,10 +1203,10 @@
 }
 
 void LocalFrame::StartPrinting(const gfx::SizeF& page_size,
-                               const gfx::SizeF& original_page_size,
+                               const gfx::SizeF& aspect_ratio,
                                float maximum_shrink_ratio) {
   DCHECK(!saved_scroll_offsets_);
-  SetPrinting(true, page_size, original_page_size, maximum_shrink_ratio);
+  SetPrinting(true, page_size, aspect_ratio, maximum_shrink_ratio);
 }
 
 void LocalFrame::EndPrinting() {
@@ -1216,7 +1216,7 @@
 
 void LocalFrame::SetPrinting(bool printing,
                              const gfx::SizeF& page_size,
-                             const gfx::SizeF& original_page_size,
+                             const gfx::SizeF& aspect_ratio,
                              float maximum_shrink_ratio) {
   // In setting printing, we should not validate resources already cached for
   // the document.  See https://bugs.webkit.org/show_bug.cgi?id=43704
@@ -1231,7 +1231,7 @@
     text_autosizer->UpdatePageInfo();
 
   if (ShouldUsePrintingLayout()) {
-    View()->ForceLayoutForPagination(page_size, original_page_size,
+    View()->ForceLayoutForPagination(page_size, aspect_ratio,
                                      maximum_shrink_ratio);
   } else {
     if (LayoutView* layout_view = View()->GetLayoutView()) {
@@ -1352,26 +1352,27 @@
 }
 
 gfx::SizeF LocalFrame::ResizePageRectsKeepingRatio(
-    const gfx::SizeF& original_size,
+    const gfx::SizeF& aspect_ratio,
     const gfx::SizeF& expected_size) const {
   auto* layout_object = ContentLayoutObject();
   if (!layout_object)
     return gfx::SizeF();
 
   bool is_horizontal = layout_object->StyleRef().IsHorizontalWritingMode();
-  float width = original_size.width();
-  float height = original_size.height();
-  if (!is_horizontal)
-    std::swap(width, height);
-  DCHECK_GT(fabs(width), std::numeric_limits<float>::epsilon());
-  float ratio = height / width;
+  float numerator =
+      is_horizontal ? aspect_ratio.height() : aspect_ratio.width();
+  float denominator =
+      is_horizontal ? aspect_ratio.width() : aspect_ratio.height();
+  DCHECK_GT(fabs(denominator), std::numeric_limits<float>::epsilon());
+  float ratio = numerator / denominator;
 
-  float result_width =
+  float inline_size =
       floorf(is_horizontal ? expected_size.width() : expected_size.height());
-  float result_height = floorf(result_width * ratio);
-  if (!is_horizontal)
-    std::swap(result_width, result_height);
-  return gfx::SizeF(result_width, result_height);
+  float block_size = floorf(inline_size * ratio);
+  if (!is_horizontal) {
+    return gfx::SizeF(block_size, inline_size);
+  }
+  return gfx::SizeF(inline_size, block_size);
 }
 
 void LocalFrame::SetPageZoomFactor(float factor) {
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index 3d2e44d..401686b 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -378,7 +378,7 @@
   // If this frame doesn't need to fit into a page size, default values are
   // used.
   void StartPrinting(const gfx::SizeF& page_size = gfx::SizeF(),
-                     const gfx::SizeF& original_page_size = gfx::SizeF(),
+                     const gfx::SizeF& aspect_ratio = gfx::SizeF(),
                      float maximum_shrink_ratio = 0);
 
   void EndPrinting();
@@ -396,7 +396,13 @@
   void EnsureSaveScrollOffset(Node&);
   void RestoreScrollOffsets();
 
-  gfx::SizeF ResizePageRectsKeepingRatio(const gfx::SizeF& original_size,
+  // Return `expected_size` adjusted to the specified `aspect_ratio`. The
+  // logical width (inline-size) of `expected_size` will be kept unmodified [*],
+  // whereas the logical height (block-size) will be adjusted if needed, to
+  // honor the aspect ratio. The values returned are rounded down to the nearest
+  // integer.
+  // [*] Except that it's rounded down to the nearest integer.
+  gfx::SizeF ResizePageRectsKeepingRatio(const gfx::SizeF& aspect_ratio,
                                          const gfx::SizeF& expected_size) const;
 
   bool InViewSourceMode() const;
@@ -917,11 +923,11 @@
 
   // Internal implementation for starting or ending printing.
   // |printing| is true when printing starts, false when printing ends.
-  // |page_size|, |original_page_size|, and |maximum_shrink_ratio| are only
+  // |page_size|, |aspect_ratio|, and |maximum_shrink_ratio| are only
   // meaningful when we should use printing layout for this frame.
   void SetPrinting(bool printing,
                    const gfx::SizeF& page_size,
-                   const gfx::SizeF& original_page_size,
+                   const gfx::SizeF& aspect_ratio,
                    float maximum_shrink_ratio);
 
   // FrameScheduler::Delegate overrides:
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index f1145da..59387f84 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2652,8 +2652,7 @@
     if (paint_artifact_compositor_ &&
         benchmark_mode ==
             PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate) {
-      paint_artifact_compositor_->SetNeedsUpdate(
-          PaintArtifactCompositorUpdateReason::kLocalFrameViewBenchmarking);
+      paint_artifact_compositor_->SetNeedsUpdate();
     }
     needed_update = !paint_artifact_compositor_ ||
                     paint_artifact_compositor_->NeedsUpdate();
@@ -3221,10 +3220,9 @@
   auto_size_info_.Clear();
 }
 
-void LocalFrameView::ForceLayoutForPagination(
-    const gfx::SizeF& page_size,
-    const gfx::SizeF& original_page_size,
-    float maximum_shrink_factor) {
+void LocalFrameView::ForceLayoutForPagination(const gfx::SizeF& page_size,
+                                              const gfx::SizeF& aspect_ratio,
+                                              float maximum_shrink_factor) {
   // Dumping externalRepresentation(m_frame->layoutObject()).ascii() is a good
   // trick to see the state of things before and after the layout
   if (LayoutView* layout_view = GetLayoutView()) {
@@ -3256,8 +3254,8 @@
                           page_size.width() * maximum_shrink_factor),
           std::min<float>(document_rect.Height().Round(),
                           page_size.height() * maximum_shrink_factor));
-      gfx::SizeF max_page_size = frame_->ResizePageRectsKeepingRatio(
-          original_page_size, expected_page_size);
+      gfx::SizeF max_page_size =
+          frame_->ResizePageRectsKeepingRatio(aspect_ratio, expected_page_size);
       page_logical_width = horizontal_writing_mode ? max_page_size.width()
                                                    : max_page_size.height();
       layout_view->SetPageSize({LayoutUnit(max_page_size.width()),
@@ -4343,11 +4341,10 @@
   return visual_viewport_or_overlay_needs_repaint_;
 }
 
-void LocalFrameView::SetPaintArtifactCompositorNeedsUpdate(
-    PaintArtifactCompositorUpdateReason reason) {
+void LocalFrameView::SetPaintArtifactCompositorNeedsUpdate() {
   LocalFrameView* root = GetFrame().LocalFrameRoot().View();
   if (root && root->paint_artifact_compositor_)
-    root->paint_artifact_compositor_->SetNeedsUpdate(reason);
+    root->paint_artifact_compositor_->SetNeedsUpdate();
 }
 
 PaintArtifactCompositor* LocalFrameView::GetPaintArtifactCompositor() const {
@@ -4668,9 +4665,7 @@
       CoreProbeSink::HasAgentsGlobal(CoreProbeSink::kInspectorLayerTreeAgent);
   if (should_enable != layer_debug_info_enabled_) {
     layer_debug_info_enabled_ = should_enable;
-    SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kLocalFrameViewUpdateLayerDebugInfo);
+    SetPaintArtifactCompositorNeedsUpdate();
     return true;
   }
 #endif
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index e0782da..19ba7c2 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -117,7 +117,6 @@
 struct PhysicalRect;
 
 enum class PaintBenchmarkMode;
-enum class PaintArtifactCompositorUpdateReason;
 
 typedef uint64_t DOMTimeStamp;
 using LayerTreeFlags = unsigned;
@@ -238,8 +237,7 @@
 
   void ForceUpdateViewportIntersections();
 
-  void SetPaintArtifactCompositorNeedsUpdate(
-      PaintArtifactCompositorUpdateReason);
+  void SetPaintArtifactCompositorNeedsUpdate();
 
   // Methods for getting/setting the size Blink should use to layout the
   // contents.
@@ -415,7 +413,7 @@
   void DisableAutoSizeMode();
 
   void ForceLayoutForPagination(const gfx::SizeF& page_size,
-                                const gfx::SizeF& original_page_size,
+                                const gfx::SizeF& aspect_ratio,
                                 float maximum_shrink_factor);
 
   // Updates the fragment anchor element based on URL's fragment identifier.
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index d766229..f42e776 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -678,13 +678,9 @@
 void WebFrameWidgetImpl::BindInputTargetClient(
     mojo::PendingReceiver<viz::mojom::blink::InputTargetClient> receiver) {
   DCHECK(!input_target_receiver_.is_bound());
-  TaskType priority = TaskType::kInternalDefault;
-  if (base::FeatureList::IsEnabled(
-          blink::features::kInputTargetClientHighPriority)) {
-    priority = TaskType::kInternalInputBlocking;
-  }
-  input_target_receiver_.Bind(std::move(receiver),
-                              local_root_->GetTaskRunner(priority));
+  input_target_receiver_.Bind(
+      std::move(receiver),
+      local_root_->GetTaskRunner(TaskType::kInternalInputBlocking));
 }
 
 void WebFrameWidgetImpl::FrameSinkIdAt(const gfx::PointF& point,
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 7d9c1463..08d615a 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
@@ -30,6 +30,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
 #include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
diff --git a/third_party/blink/renderer/core/html/fenced_frame/fence.h b/third_party/blink/renderer/core/html/fenced_frame/fence.h
index aa9554f7..498e414b 100644
--- a/third_party/blink/renderer/core/html/fenced_frame/fence.h
+++ b/third_party/blink/renderer/core/html/fenced_frame/fence.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FENCED_FRAME_FENCE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FENCED_FRAME_FENCE_H_
 
+#include "base/gtest_prod_util.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
diff --git a/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h b/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h
index 1865dba..819e310 100644
--- a/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h
+++ b/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FENCED_FRAME_HTML_FENCED_FRAME_ELEMENT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FENCED_FRAME_HTML_FENCED_FRAME_ELEMENT_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/notreached.h"
 #include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
 #include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom-blink.h"
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
index 58b40e4..bad789a 100644
--- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
@@ -1573,9 +1573,7 @@
   client.SetCursor(PointerCursor(), GetFrame());
 
   if (auto* frame_view = frame_impl_->GetFrameView()) {
-    frame_view->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kInspectorOverlayAgentDisableFrameOverlay);
+    frame_view->SetPaintArtifactCompositorNeedsUpdate();
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index 9708b79..24ba548 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -27,6 +27,7 @@
 
 #include "base/check_op.h"
 #include "base/dcheck_is_on.h"
+#include "base/gtest_prod_util.h"
 #include "base/notreached.h"
 #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
diff --git a/third_party/blink/renderer/core/layout/layout_image_resource.h b/third_party/blink/renderer/core/layout/layout_image_resource.h
index c59fe61..97bbb37 100644
--- a/third_party/blink/renderer/core/layout/layout_image_resource.h
+++ b/third_party/blink/renderer/core/layout/layout_image_resource.h
@@ -27,6 +27,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_IMAGE_RESOURCE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_IMAGE_RESOURCE_H_
 
+#include "base/gtest_prod_util.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
 #include "third_party/blink/renderer/core/style/style_image.h"
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
index 4380904..ede85ef 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
@@ -876,6 +876,7 @@
             layout_object);
         item.SetTextType(NGTextType::kFlowControl);
         start = end;
+        is_score_line_break_disabled_ = true;
         continue;
       }
       // ZWNJ splits item, but it should be text.
@@ -1075,6 +1076,7 @@
   // block-in-inline, before it and after it separately. See
   // `NGParagraphLineBreaker`.
   is_bisect_line_break_disabled_ = true;
+  is_score_line_break_disabled_ = true;
 }
 
 template <typename OffsetMappingBuilder>
@@ -1085,6 +1087,7 @@
   // Floats/exclusions require computing line heights, which is currently
   // skipped during the bisect. See `NGParagraphLineBreaker`.
   is_bisect_line_break_disabled_ = true;
+  is_score_line_break_disabled_ = true;
 }
 
 template <typename OffsetMappingBuilder>
@@ -1416,6 +1419,7 @@
   data->is_block_level_ = IsBlockLevel();
   data->changes_may_affect_earlier_lines_ = HasUnicodeBidiPlainText();
   data->is_bisect_line_break_disabled_ = is_bisect_line_break_disabled_;
+  data->is_score_line_break_disabled_ = is_score_line_break_disabled_;
 
 #if DCHECK_IS_ON()
   data->CheckConsistency();
@@ -1431,6 +1435,7 @@
   // Floats/exclusions require computing line heights, which is currently
   // skipped during the bisect. See `NGParagraphLineBreaker`.
   is_bisect_line_break_disabled_ = true;
+  is_score_line_break_disabled_ = true;
 }
 
 template <typename OffsetMappingBuilder>
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
index 32cb9f7..713b038 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
@@ -200,6 +200,7 @@
   bool is_block_level_ = true;
   bool has_unicode_bidi_plain_text_ = false;
   bool is_bisect_line_break_disabled_ = false;
+  bool is_score_line_break_disabled_ = false;
 
   // Append a character.
   // Currently this function is for adding control characters such as
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
index d015a52..472480a7 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -1515,6 +1515,9 @@
     ShapeText(first_line_items);
 
   data->first_line_items_ = first_line_items;
+  // The score line breaker can't apply different styles by different line
+  // breaking.
+  data->is_score_line_break_disabled_ = true;
 }
 
 void NGInlineNode::ShapeTextIncludingFirstLine(
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
index 3d9bfbe..e8caca4b 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
@@ -106,6 +106,9 @@
   bool IsBisectLineBreakDisabled() const {
     return Data().IsBisectLineBreakDisabled();
   }
+  bool IsScoreLineBreakDisabled() const {
+    return Data().IsScoreLineBreakDisabled();
+  }
 
   // @return if this node can contain the "first formatted line".
   // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
index 95dc735..0d83c17 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
@@ -32,6 +32,9 @@
   bool IsBisectLineBreakDisabled() const {
     return is_bisect_line_break_disabled_;
   }
+  bool IsScoreLineBreakDisabled() const {
+    return is_score_line_break_disabled_;
+  }
 
   const NGInlineItemsData& ItemsData(bool is_first_line) const {
     return !is_first_line || !first_line_items_
@@ -91,6 +94,7 @@
   unsigned changes_may_affect_earlier_lines_ : 1;
 
   unsigned is_bisect_line_break_disabled_ : 1;
+  unsigned is_score_line_break_disabled_ : 1;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_paragraph_line_breaker_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_paragraph_line_breaker_test.cc
index d2a33ad..b3be556 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_paragraph_line_breaker_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_paragraph_line_breaker_test.cc
@@ -13,13 +13,12 @@
 
 class NGParagraphLineBreakerTest : public RenderingTest {
  public:
-  absl::optional<LayoutUnit> AttemptParagraphBalancing(const char* id) {
-    auto* layout_object = To<LayoutBlockFlow>(GetLayoutObjectByElementId(id));
+  absl::optional<LayoutUnit> AttemptParagraphBalancing(
+      const NGInlineNode& node) {
     const NGPhysicalBoxFragment* fragment =
-        layout_object->GetPhysicalFragment(0);
+        node.GetLayoutBox()->GetPhysicalFragment(0);
     const LayoutUnit width = fragment->Size().width;
 
-    NGInlineNode node(layout_object);
     NGConstraintSpaceBuilder builder(
         WritingMode::kHorizontalTb,
         {WritingMode::kHorizontalTb, TextDirection::kLtr},
@@ -32,7 +31,7 @@
   }
 };
 
-TEST_F(NGParagraphLineBreakerTest, ShouldFailByBlockInInline) {
+TEST_F(NGParagraphLineBreakerTest, IsDisabledByBlockInInline) {
   SetBodyInnerHTML(R"HTML(
     <!DOCTYPE html>
     <style>
@@ -51,10 +50,36 @@
       </span>
     </div>
   )HTML");
-  EXPECT_FALSE(AttemptParagraphBalancing("target"));
+  const NGInlineNode target = GetInlineNodeByElementId("target");
+  EXPECT_TRUE(target.IsBisectLineBreakDisabled());
+  EXPECT_TRUE(target.IsScoreLineBreakDisabled());
+  EXPECT_FALSE(AttemptParagraphBalancing(target));
 }
 
-TEST_F(NGParagraphLineBreakerTest, ShouldFailByFloat) {
+TEST_F(NGParagraphLineBreakerTest, IsDisabledByFirstLine) {
+  SetBodyInnerHTML(R"HTML(
+    <!DOCTYPE html>
+    <style>
+    #target {
+      font-size: 10px;
+      width: 10ch;
+    }
+    #target::first-line {
+      font-weight: bold;
+    }
+    </style>
+    <div id="target">
+      1234 6789
+      1234 6789
+    </div>
+  )HTML");
+  const NGInlineNode target = GetInlineNodeByElementId("target");
+  EXPECT_FALSE(target.IsBisectLineBreakDisabled());
+  EXPECT_TRUE(target.IsScoreLineBreakDisabled());
+  EXPECT_TRUE(AttemptParagraphBalancing(target));
+}
+
+TEST_F(NGParagraphLineBreakerTest, IsDisabledByFloat) {
   SetBodyInnerHTML(R"HTML(
     <!DOCTYPE html>
     <style>
@@ -70,10 +95,13 @@
       1234 6789
     </div>
   )HTML");
-  EXPECT_FALSE(AttemptParagraphBalancing("target"));
+  const NGInlineNode target = GetInlineNodeByElementId("target");
+  EXPECT_TRUE(target.IsBisectLineBreakDisabled());
+  EXPECT_TRUE(target.IsScoreLineBreakDisabled());
+  EXPECT_FALSE(AttemptParagraphBalancing(target));
 }
 
-TEST_F(NGParagraphLineBreakerTest, ShouldFailByForcedBreak) {
+TEST_F(NGParagraphLineBreakerTest, IsDisabledByForcedBreak) {
   SetBodyInnerHTML(R"HTML(
     <!DOCTYPE html>
     <style>
@@ -88,10 +116,13 @@
       1234 6789
     </div>
   )HTML");
-  EXPECT_FALSE(AttemptParagraphBalancing("target"));
+  const NGInlineNode target = GetInlineNodeByElementId("target");
+  EXPECT_TRUE(target.IsBisectLineBreakDisabled());
+  EXPECT_FALSE(target.IsScoreLineBreakDisabled());
+  EXPECT_FALSE(AttemptParagraphBalancing(target));
 }
 
-TEST_F(NGParagraphLineBreakerTest, ShouldFailByInitialLetter) {
+TEST_F(NGParagraphLineBreakerTest, IsDisabledByInitialLetter) {
   SetBodyInnerHTML(R"HTML(
     <!DOCTYPE html>
     <style>
@@ -108,7 +139,28 @@
       1234 6789
     </div>
   )HTML");
-  EXPECT_FALSE(AttemptParagraphBalancing("target"));
+  const NGInlineNode target = GetInlineNodeByElementId("target");
+  EXPECT_TRUE(target.IsBisectLineBreakDisabled());
+  EXPECT_TRUE(target.IsScoreLineBreakDisabled());
+  EXPECT_FALSE(AttemptParagraphBalancing(target));
+}
+
+TEST_F(NGParagraphLineBreakerTest, IsDisabledByTabulationCharacters) {
+  SetBodyInnerHTML(R"HTML(
+    <!DOCTYPE html>
+    <style>
+    #target {
+      font-size: 10px;
+      width: 10ch;
+      white-space: pre-wrap;
+    }
+    </style>
+    <div id="target">1234 6789&#0009;1234 6789</div>
+  )HTML");
+  const NGInlineNode target = GetInlineNodeByElementId("target");
+  EXPECT_FALSE(target.IsBisectLineBreakDisabled());
+  EXPECT_TRUE(target.IsScoreLineBreakDisabled());
+  EXPECT_TRUE(AttemptParagraphBalancing(target));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index 9d7c441..00600cd 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -224,9 +224,8 @@
 inline bool NeedsLineInfoList(const NGInlineNode& node) {
   const TextWrap wrap = node.Style().GetTextWrap();
   if (UNLIKELY(wrap == TextWrap::kPretty)) {
-    // TODO(crbug.com/1432798): Needs more conditions not to needed it.
     DCHECK(RuntimeEnabledFeatures::CSSTextWrapPrettyEnabled());
-    return true;
+    return !node.IsScoreLineBreakDisabled();
   }
   return false;
 }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc
index db9d65f..a14f1ec 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc
@@ -117,6 +117,7 @@
   DCHECK_EQ(HasTransformRelatedProperty(),
             StyleRef().HasTransformRelatedPropertyForSVG());
 
+  TransformHelper::UpdateOffsetPath(*GetElement(), old_style);
   transform_uses_reference_box_ =
       TransformHelper::DependsOnReferenceBox(StyleRef());
 
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc
index 2b36b62..ef487d41 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc
@@ -64,6 +64,7 @@
 void LayoutSVGImage::StyleDidChange(StyleDifference diff,
                                     const ComputedStyle* old_style) {
   NOT_DESTROYED();
+  TransformHelper::UpdateOffsetPath(*GetElement(), old_style);
   transform_uses_reference_box_ =
       TransformHelper::DependsOnReferenceBox(StyleRef());
   LayoutSVGModelObject::StyleDidChange(diff, old_style);
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
index cd037eb..006c06a 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
@@ -77,6 +77,7 @@
   NOT_DESTROYED();
   LayoutSVGModelObject::StyleDidChange(diff, old_style);
 
+  TransformHelper::UpdateOffsetPath(*GetElement(), old_style);
   transform_uses_reference_box_ =
       TransformHelper::DependsOnReferenceBox(StyleRef());
   SVGResources::UpdatePaints(*this, old_style, StyleRef());
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.cc
index 5ad5280a..5941ac26 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.cc
@@ -123,6 +123,7 @@
   NOT_DESTROYED();
   LayoutSVGContainer::StyleDidChange(diff, old_style);
 
+  TransformHelper::UpdateOffsetPath(*GetElement(), old_style);
   transform_uses_reference_box_ =
       TransformHelper::DependsOnReferenceBox(StyleRef());
 }
diff --git a/third_party/blink/renderer/core/layout/svg/transform_helper.cc b/third_party/blink/renderer/core/layout/svg/transform_helper.cc
index d016bb1..46b5a7c 100644
--- a/third_party/blink/renderer/core/layout/svg/transform_helper.cc
+++ b/third_party/blink/renderer/core/layout/svg/transform_helper.cc
@@ -5,8 +5,11 @@
 #include "third_party/blink/renderer/core/layout/svg/transform_helper.h"
 
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-blink.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/style/reference_offset_path_operation.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
@@ -24,6 +27,29 @@
          style.GetTransformOrigin().Y().IsFixed();
 }
 
+// static
+void TransformHelper::UpdateOffsetPath(SVGElement& element,
+                                       const ComputedStyle* old_style) {
+  const ComputedStyle& new_style = element.ComputedStyleRef();
+  OffsetPathOperation* new_offset = new_style.OffsetPath();
+  OffsetPathOperation* old_offset =
+      old_style ? old_style->OffsetPath() : nullptr;
+  if (!new_offset && !old_offset) {
+    return;
+  }
+  const bool had_resource_info = element.GetSVGResourceClient();
+  if (auto* reference_offset =
+          DynamicTo<ReferenceOffsetPathOperation>(new_offset)) {
+    reference_offset->AddClient(element.EnsureSVGResourceClient());
+  }
+  if (had_resource_info) {
+    if (auto* old_reference_offset =
+            DynamicTo<ReferenceOffsetPathOperation>(old_offset)) {
+      old_reference_offset->RemoveClient(*element.GetSVGResourceClient());
+    }
+  }
+}
+
 bool TransformHelper::DependsOnReferenceBox(const ComputedStyle& style) {
   // We're passing kExcludeMotionPath here because we're checking that
   // explicitly later.
diff --git a/third_party/blink/renderer/core/layout/svg/transform_helper.h b/third_party/blink/renderer/core/layout/svg/transform_helper.h
index 8a1708c..c475bb8 100644
--- a/third_party/blink/renderer/core/layout/svg/transform_helper.h
+++ b/third_party/blink/renderer/core/layout/svg/transform_helper.h
@@ -15,12 +15,14 @@
 
 namespace blink {
 
+class SVGElement;
 class LayoutObject;
 
 class TransformHelper {
   STATIC_ONLY(TransformHelper);
 
  public:
+  static void UpdateOffsetPath(SVGElement&, const ComputedStyle*);
   // Returns true if the passed in ComputedStyle has a transform that needs to
   // resolve against the reference box.
   static bool DependsOnReferenceBox(const ComputedStyle&);
diff --git a/third_party/blink/renderer/core/page/print_context.cc b/third_party/blink/renderer/core/page/print_context.cc
index 51e910c9..a143b06 100644
--- a/third_party/blink/renderer/core/page/print_context.cc
+++ b/third_party/blink/renderer/core/page/print_context.cc
@@ -151,10 +151,10 @@
   // without going back to screen mode.
   is_printing_ = true;
 
-  gfx::SizeF original_page_size(width, height);
-  gfx::SizeF min_layout_size = frame_->ResizePageRectsKeepingRatio(
-      original_page_size, gfx::SizeF(width * kPrintingMinimumShrinkFactor,
-                                     height * kPrintingMinimumShrinkFactor));
+  gfx::SizeF aspect_ratio(width, height);
+  gfx::SizeF floored_min_layout_size = frame_->ResizePageRectsKeepingRatio(
+      aspect_ratio, gfx::SizeF(width * kPrintingMinimumShrinkFactor,
+                               height * kPrintingMinimumShrinkFactor));
 
   const Settings* settings = frame_->GetSettings();
   DCHECK(settings);
@@ -164,7 +164,7 @@
   // This changes layout, so callers need to make sure that they don't paint to
   // screen while in printing mode.
   frame_->StartPrinting(
-      min_layout_size, original_page_size,
+      floored_min_layout_size, aspect_ratio,
       printingMaximumShrinkFactor / kPrintingMinimumShrinkFactor);
 }
 
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
index 31d6315..20fea98 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
@@ -392,9 +392,7 @@
   DCHECK(node_);
   if (auto* frame_view = node_->GetDocument().View()) {
     frame_view->SetVisualViewportOrOverlayNeedsRepaint();
-    frame_view->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kLinkHighlightImplNeedsCompositingUpdate);
+    frame_view->SetPaintArtifactCompositorNeedsUpdate();
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc b/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc
index 2d672c8..ad7c702 100644
--- a/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc
@@ -1032,8 +1032,7 @@
 
   auto* canvas = GetDocument().getElementById("canvas");
   canvas->setAttribute(html_names::kClassAttr, "updated");
-  GetDocument().View()->SetPaintArtifactCompositorNeedsUpdate(
-      PaintArtifactCompositorUpdateReason::kTest);
+  GetDocument().View()->SetPaintArtifactCompositorNeedsUpdate();
 
   UpdateAllLifecyclePhasesForTest();
   EXPECT_FALSE(
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 6db7ffb..a4e6f45 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -460,9 +460,7 @@
 
   if (auto* scrolling_coordinator = GetScrollingCoordinator()) {
     if (!scrolling_coordinator->UpdateCompositorScrollOffset(*frame, *this)) {
-      GetLayoutBox()->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(
-          PaintArtifactCompositorUpdateReason::
-              kPaintLayerScrollableAreaUpdateScrollOffset);
+      GetLayoutBox()->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
     }
   }
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 6e8d525..5e2523d 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -162,9 +162,7 @@
 
   if (property_changed >
       PaintPropertyChangeType::kChangedOnlyCompositedValues) {
-    main_frame_view.SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kVisualViewportPaintPropertyTreeBuilderUpdate);
+    main_frame_view.SetPaintArtifactCompositorNeedsUpdate();
   }
 
 #if DCHECK_IS_ON()
@@ -3452,32 +3450,6 @@
   }
 }
 
-namespace {
-PaintArtifactCompositorUpdateReason PACUpdateReasonForPaintPropertyChange(
-    PaintPropertyChangeType change) {
-  switch (change) {
-    case (PaintPropertyChangeType::kChangedOnlyNonRerasterValues):
-      return PaintArtifactCompositorUpdateReason::
-          kPaintPropertyTreeBuilderPaintPropertyChangedOnlyNonRerasterValues;
-    case (PaintPropertyChangeType::kChangedOnlySimpleValues):
-      return PaintArtifactCompositorUpdateReason::
-          kPaintPropertyTreeBuilderPaintPropertyChangedOnlySimpleValues;
-    case (PaintPropertyChangeType::kChangedOnlyValues):
-      return PaintArtifactCompositorUpdateReason::
-          kPaintPropertyTreeBuilderPaintPropertyChangedOnlyValues;
-    case (PaintPropertyChangeType::kNodeAddedOrRemoved):
-      return PaintArtifactCompositorUpdateReason::
-          kPaintPropertyTreeBuilderPaintPropertyAddedOrRemoved;
-    default:
-      // The other values for PaintPropertyChangeType should not cause a
-      // paint artifact compositor update.
-      NOTREACHED();
-  }
-  return PaintArtifactCompositorUpdateReason::
-      kPaintPropertyTreeBuilderPaintPropertyChanged;
-}
-}  // namespace
-
 bool PaintPropertyTreeBuilder::ScheduleDeferredTransformNodeUpdate(
     LayoutObject& object) {
   if (!base::FeatureList::IsEnabled(features::kFastPathPaintPropertyUpdates))
@@ -3533,9 +3505,7 @@
 
   if (effective_change_type >=
       PaintPropertyChangeType::kChangedOnlySimpleValues) {
-    object.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kPaintPropertyTreeBuilderPaintPropertyChanged);
+    object.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
   }
 
   PaintPropertiesChangeInfo properties_changed;
@@ -3566,9 +3536,7 @@
 
   if (effective_change_type >=
       PaintPropertyChangeType::kChangedOnlySimpleValues) {
-    object.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::
-            kPaintPropertyTreeBuilderPaintPropertyChanged);
+    object.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
   }
 }
 
@@ -3595,8 +3563,7 @@
   }
 
   if (max_change > PaintPropertyChangeType::kChangedOnlyCompositedValues) {
-    auto reason = PACUpdateReasonForPaintPropertyChange(max_change);
-    object_.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(reason);
+    object_.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
   }
 
   if (!RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled()) {
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h
index 413176e9..6625eda 100644
--- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h
@@ -9,6 +9,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TIMING_IMAGE_PAINT_TIMING_DETECTOR_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TIMING_IMAGE_PAINT_TIMING_DETECTOR_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/time/time.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/performance/largest_contentful_paint_type.h"
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h
index 322bfee2..d529ca9 100644
--- a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h
@@ -8,6 +8,7 @@
 #include <queue>
 
 #include "base/auto_reset.h"
+#include "base/gtest_prod_util.h"
 #include "base/time/time.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/performance/largest_contentful_paint_type.h"
diff --git a/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc b/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc
index 19eca52..2b81ab0 100644
--- a/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc
+++ b/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc
@@ -5,9 +5,12 @@
 #include "third_party/blink/renderer/core/script/detect_javascript_frameworks.h"
 
 #include "base/feature_list.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/loader/loading_behavior_flag.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
+#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
@@ -15,6 +18,11 @@
 #include "third_party/blink/renderer/platform/bindings/v8_binding.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+#include "v8-container.h"
+#include "v8-local-handle.h"
+#include "v8-object.h"
+#include "v8-primitive.h"
+#include "v8-regexp.h"
 #include "v8/include/v8.h"
 
 namespace blink {
@@ -63,14 +71,17 @@
 }
 
 inline void CheckAttributeMatches(const Element& element,
-                                  int& loading_behavior_flag) {
+                                  int& loading_behavior_flag,
+                                  AtomicString& detected_ng_version) {
   DEFINE_STATIC_LOCAL(QualifiedName, ng_version,
                       (g_null_atom, "ng-version", g_null_atom));
   DEFINE_STATIC_LOCAL(QualifiedName, data_reactroot,
                       (g_null_atom, "data-reactroot", g_null_atom));
   static constexpr char kSvelte[] = "svelte-";
-  if (element.FastHasAttribute(ng_version))
+  if (element.FastHasAttribute(ng_version)) {
     loading_behavior_flag |= kLoadingBehaviorAngularFrameworkUsed;
+    detected_ng_version = element.FastGetAttribute(ng_version);
+  }
   if (element.FastHasAttribute(data_reactroot))
     loading_behavior_flag |= kLoadingBehaviorReactFrameworkUsed;
   if (element.GetClassAttribute().StartsWith(kSvelte))
@@ -120,6 +131,7 @@
                                        int& loading_behavior_flag,
                                        bool& has_nextjs_id) {
   static constexpr char kVueData[] = "Vue";
+  static constexpr char kVue3Data[] = "__VUE__";
   static constexpr char kReactData[] = "React";
   if (has_nextjs_id && IsFrameworkVariableUsed(context, kNextjsData))
     loading_behavior_flag |= kLoadingBehaviorNextJSFrameworkUsed;
@@ -129,8 +141,10 @@
     loading_behavior_flag |= kLoadingBehaviorSapperFrameworkUsed;
   if (IsFrameworkVariableUsed(context, kVuepressData))
     loading_behavior_flag |= kLoadingBehaviorVuePressFrameworkUsed;
-  if (IsFrameworkVariableUsed(context, kVueData))
+  if (IsFrameworkVariableUsed(context, kVueData) ||
+      IsFrameworkVariableUsed(context, kVue3Data)) {
     loading_behavior_flag |= kLoadingBehaviorVueFrameworkUsed;
+  }
   // TODO(npm): Add check for window.React.Component, not just window.React.
   if (IsFrameworkVariableUsed(context, kReactData))
     loading_behavior_flag |= kLoadingBehaviorReactFrameworkUsed;
@@ -153,18 +167,145 @@
   }
 }
 
+absl::optional<int64_t> ExtractVersion(v8::Local<v8::RegExp> regexp,
+                                       v8::Local<v8::Context> context,
+                                       v8::Local<v8::Value> version) {
+  v8::Local<v8::Object> groups;
+  v8::Local<v8::Value> major;
+  v8::Local<v8::Value> minor;
+  bool success =
+      regexp->Exec(context, version.As<v8::String>()).ToLocal(&groups);
+  if (!success || !groups->IsArray()) {
+    return absl::nullopt;
+  }
+  v8::Local<v8::Array> groups_array = groups.As<v8::Array>();
+  if (!groups_array->Get(context, 1).ToLocal(&major) ||
+      !groups_array->Get(context, 2).ToLocal(&minor) || !major->IsString() ||
+      !minor->IsString()) {
+    return absl::nullopt;
+  }
+
+  v8::Local<v8::Value> major_number;
+  v8::Local<v8::Value> minor_number;
+  if (!major->ToNumber(context).ToLocal(&major_number) ||
+      !minor->ToNumber(context).ToLocal(&minor_number)) {
+    return absl::nullopt;
+  }
+
+  // Major & minor versions are clamped to 8bits to avoid using this as a
+  // vector to identify users.
+  return ((major_number->IntegerValue(context).FromMaybe(0) & 0xff) << 8) |
+         (minor_number->IntegerValue(context).FromMaybe(0) & 0xff);
+}
+
+void DetectFrameworkVersions(Document& document,
+                             v8::Local<v8::Context> context,
+                             v8::Isolate* isolate,
+                             int detected_flags,
+                             const AtomicString& detected_ng_version) {
+  if (!document.UkmRecorder() ||
+      document.UkmSourceID() == ukm::kInvalidSourceId) {
+    return;
+  }
+  ukm::builders::Blink_JavaScriptFramework_Versions builder(
+      document.UkmSourceID());
+  v8::Local<v8::Object> global = context->Global();
+  static constexpr char kVersionPattern[] = "([0-9]+)\\.([0-9]+)";
+  v8::Local<v8::RegExp> version_regexp =
+      v8::RegExp::New(context, V8AtomicString(isolate, kVersionPattern),
+                      v8::RegExp::kNone)
+          .ToLocalChecked();
+  bool detected = false;
+
+  auto SafeGetProperty = [&](v8::Local<v8::Value> object,
+                             const char* prop_name) -> v8::Local<v8::Value> {
+    if (object.IsEmpty() || !object->IsObject()) {
+      return v8::Undefined(isolate);
+    }
+
+    v8::Local<v8::Value> value;
+    if (!object.As<v8::Object>()
+             ->GetRealNamedProperty(context, V8AtomicString(isolate, prop_name))
+             .ToLocal(&value)) {
+      return v8::Undefined(isolate);
+    }
+
+    return value;
+  };
+
+  if (detected_flags & kLoadingBehaviorNextJSFrameworkUsed) {
+    static constexpr char kNext[] = "next";
+    static constexpr char kVersion[] = "version";
+    v8::Local<v8::Value> version_string =
+        SafeGetProperty(SafeGetProperty(global, kNext), kVersion);
+    if (!version_string.IsEmpty() && version_string->IsString()) {
+      absl::optional<int64_t> version =
+          ExtractVersion(version_regexp, context, version_string);
+      if (version.has_value()) {
+        detected = true;
+        builder.SetNextJSVersion(version.value());
+      }
+    }
+  }
+
+  if (!detected_ng_version.IsNull()) {
+    absl::optional<int64_t> version = ExtractVersion(
+        version_regexp, context,
+        v8::String::NewFromUtf8(isolate,
+                                detected_ng_version.GetString().Utf8().c_str())
+            .FromMaybe(v8::String::Empty(isolate)));
+    if (version.has_value()) {
+      detected = true;
+      builder.SetAngularVersion(version.value());
+    }
+  }
+
+  if (detected_flags & kLoadingBehaviorVueFrameworkUsed) {
+    static constexpr char kVue2[] = "Vue";
+    static constexpr char kVersion[] = "version";
+    if (global->HasRealNamedProperty(context, V8AtomicString(isolate, kVue2))
+            .FromMaybe(false)) {
+      v8::Local<v8::Value> version_string =
+          SafeGetProperty(SafeGetProperty(global, kVue2), kVersion);
+      if (!version_string.IsEmpty() && version_string->IsString()) {
+        absl::optional<int64_t> version =
+            ExtractVersion(version_regexp, context, version_string);
+        if (version.has_value()) {
+          detected = true;
+          builder.SetVueVersion(version.value());
+        }
+      }
+    } else {
+      static constexpr char kVue3[] = "__VUE__";
+      bool vue3 = false;
+      if (global->HasRealNamedProperty(context, V8AtomicString(isolate, kVue3))
+              .To(&vue3) &&
+          vue3) {
+        detected = true;
+        // Vue3.x doesn't provide a detectable minor version number.
+        builder.SetVueVersion(0x300);
+      }
+    }
+  }
+
+  if (detected) {
+    builder.Record(document.UkmRecorder());
+  }
+}
+
 void TraverseTreeForFrameworks(Document& document,
                                v8::Local<v8::Context> context) {
   v8::Isolate* isolate = context->GetIsolate();
   v8::TryCatch try_catch(isolate);
   int loading_behavior_flag = kLoadingBehaviorNone;
+  AtomicString detected_ng_version;
   bool has_nextjs_id = false;
   if (!document.documentElement())
     return;
   DOMDataStore& dom_data_store = DOMWrapperWorld::MainWorld().DomDataStore();
   for (Element& element :
        ElementTraversal::InclusiveDescendantsOf(*document.documentElement())) {
-    CheckAttributeMatches(element, loading_behavior_flag);
+    CheckAttributeMatches(element, loading_behavior_flag, detected_ng_version);
     CheckPropertyMatches(element, dom_data_store, context, isolate,
                          loading_behavior_flag);
   }
@@ -173,6 +314,8 @@
                              has_nextjs_id);
   DCHECK(!try_catch.HasCaught());
   DidObserveLoadingBehaviors(document, loading_behavior_flag);
+  DetectFrameworkVersions(document, context, isolate, loading_behavior_flag,
+                          detected_ng_version);
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc
index 9106431c..dd1825d 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.h"
 
+#include "base/gtest_prod_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
 #include "third_party/blink/renderer/core/scroll/scrollbar.h"
diff --git a/third_party/blink/renderer/core/svg/svg_element.h b/third_party/blink/renderer/core/svg/svg_element.h
index c4b9837..84071a8 100644
--- a/third_party/blink/renderer/core/svg/svg_element.h
+++ b/third_party/blink/renderer/core/svg/svg_element.h
@@ -23,6 +23,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_SVG_SVG_ELEMENT_H_
 
 #include "base/dcheck_is_on.h"
+#include "base/gtest_prod_util.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
diff --git a/third_party/blink/renderer/core/testing/core_unit_test_helper.h b/third_party/blink/renderer/core/testing/core_unit_test_helper.h
index 5ebfc33f..f48aece 100644
--- a/third_party/blink/renderer/core/testing/core_unit_test_helper.h
+++ b/third_party/blink/renderer/core/testing/core_unit_test_helper.h
@@ -18,7 +18,7 @@
 #include "third_party/blink/renderer/core/layout/geometry/logical_rect.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
-#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
 #include "third_party/blink/renderer/core/loader/empty_clients.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/testing/layer_tree_host_embedder.h"
@@ -151,6 +151,14 @@
     return To<LayoutBox>(GetLayoutObjectByElementId(id));
   }
 
+  LayoutBlockFlow* GetLayoutBlockFlowByElementId(const char* id) const {
+    return To<LayoutBlockFlow>(GetLayoutObjectByElementId(id));
+  }
+
+  NGInlineNode GetInlineNodeByElementId(const char* id) const {
+    return NGInlineNode(GetLayoutBlockFlowByElementId(id));
+  }
+
   PaintLayer* GetPaintLayerByElementId(const char* id) {
     return To<LayoutBoxModelObject>(GetLayoutObjectByElementId(id))->Layer();
   }
diff --git a/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc b/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
index 8f5e195..fab6e16 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
@@ -240,8 +240,7 @@
 
   // Ensure paint artifact compositor does an update, since that's the mechanism
   // we use to pass transition requests to the compositor.
-  document->View()->SetPaintArtifactCompositorNeedsUpdate(
-      PaintArtifactCompositorUpdateReason::kViewTransitionNotifyChanges);
+  document->View()->SetPaintArtifactCompositorNeedsUpdate();
 }
 
 VectorOf<std::unique_ptr<ViewTransitionRequest>>
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.h b/third_party/blink/renderer/core/workers/dedicated_worker.h
index 630e796..fc47fbcc 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.h
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.h
@@ -6,7 +6,9 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_DEDICATED_WORKER_H_
 
 #include <memory>
+
 #include "base/functional/function_ref.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/network/public/mojom/content_security_policy.mojom-blink-forward.h"
diff --git a/third_party/blink/renderer/core/workers/threaded_worklet_test.cc b/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
index f40d761e..7d7e023 100644
--- a/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
+++ b/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
@@ -4,6 +4,7 @@
 
 #include <bitset>
 
+#include "base/gtest_prod_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index d0190699..e7543c14 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -133,6 +133,7 @@
     "//third_party/blink/renderer/modules/picture_in_picture",
     "//third_party/blink/renderer/modules/plugins",
     "//third_party/blink/renderer/modules/presentation",
+    "//third_party/blink/renderer/modules/private_attribution",
     "//third_party/blink/renderer/modules/push_messaging",
     "//third_party/blink/renderer/modules/quota",
     "//third_party/blink/renderer/modules/remoteplayback",
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 1cf3e6d..990fa45 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -3458,10 +3458,6 @@
     return true;
   }
 
-  if (IsExcludedByFormControlsFilter()) {
-    return false;
-  }
-
   const Node* node = GetNode();
 
   if (!node) {
@@ -3486,6 +3482,43 @@
     return true;
   }
 
+  // Labels are sometimes marked ignored, to prevent duplication when the AT
+  // reads the label and the control it labels (see
+  // AXNodeObject::IsRedundantLabel), but we will need them to calculate the
+  // name of the control.
+  if (IsA<HTMLLabelElement>(node)) {
+    return true;
+  }
+
+  // Custom elements and their children are included in the tree.
+  // <slot>s and their children are included in the tree.
+  // Also children of <label> elements, for accname calculation purposes.
+  // This checks to see whether this is a child of one of those.
+  if (Node* parent_node = LayoutTreeBuilderTraversal::Parent(*node)) {
+    if (parent_node->IsCustomElement() ||
+        ToHTMLSlotElementIfSupportsAssignmentOrNull(parent_node)) {
+      return true;
+    }
+    // <span>s are ignored because they are considered uninteresting. Do not add
+    // them back inside labels.
+    if (IsA<HTMLLabelElement>(parent_node) && !IsA<HTMLSpanElement>(node)) {
+      return true;
+    }
+    // Simplify AXNodeObject::AddImageMapChildren() -- it will only need to deal
+    // with included children.
+    if (IsA<HTMLMapElement>(parent_node)) {
+      return true;
+    }
+    // Necessary to calculate the accessible description of a ruby node.
+    if (IsA<HTMLRTElement>(parent_node)) {
+      return true;
+    }
+  }
+
+  if (IsExcludedByFormControlsFilter()) {
+    return false;
+  }
+
   // Allow the browser side ax tree to access "visibility: [hidden|collapse]"
   // and "display: none" nodes. This is useful for APIs that return the node
   // referenced by aria-labeledby and aria-describedby.
@@ -3519,35 +3552,6 @@
       return true;
   }
 
-  // Labels are sometimes marked ignored, to prevent duplication when the AT
-  // reads the label and the control it labels (see
-  // AXNodeObject::IsRedundantLabel), but we will need them to calculate the
-  // name of the control.
-  if (IsA<HTMLLabelElement>(node))
-    return true;
-
-  // Custom elements and their children are included in the tree.
-  // <slot>s and their children are included in the tree.
-  // Also children of <label> elements, for accname calculation purposes.
-  // This checks to see whether this is a child of one of those.
-  if (Node* parent_node = LayoutTreeBuilderTraversal::Parent(*node)) {
-    if (parent_node->IsCustomElement() ||
-        ToHTMLSlotElementIfSupportsAssignmentOrNull(parent_node)) {
-      return true;
-    }
-    // <span>s are ignored because they are considered uninteresting. Do not add
-    // them back inside labels.
-    if (IsA<HTMLLabelElement>(parent_node) && !IsA<HTMLSpanElement>(node))
-      return true;
-    // Simplify AXNodeObject::AddImageMapChildren() -- it will only need to deal
-    // with included children.
-    if (IsA<HTMLMapElement>(parent_node))
-      return true;
-    // Necessary to calculate the accessible description of a ruby node.
-    if (IsA<HTMLRTElement>(parent_node))
-      return true;
-  }
-
   if (const Element* owner = node->OwnerShadowHost()) {
     // The ignored state of media controls can change without a layout update.
     // Keep them in the tree at all times so that the serializer isn't
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 1d4c20f..d69389e 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -34,6 +34,7 @@
 #include <utility>
 
 #include "base/dcheck_is_on.h"
+#include "base/gtest_prod_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/web/web_ax_enums.h"
diff --git a/third_party/blink/renderer/modules/document_picture_in_picture/document_picture_in_picture_options.idl b/third_party/blink/renderer/modules/document_picture_in_picture/document_picture_in_picture_options.idl
index 8ffd2df..de076d6 100644
--- a/third_party/blink/renderer/modules/document_picture_in_picture/document_picture_in_picture_options.idl
+++ b/third_party/blink/renderer/modules/document_picture_in_picture/document_picture_in_picture_options.idl
@@ -7,6 +7,5 @@
 dictionary DocumentPictureInPictureOptions {
   [EnforceRange] unsigned long long width = 0;
   [EnforceRange] unsigned long long height = 0;
-  double initialAspectRatio = 0.0;
   boolean copyStyleSheets = false;
 };
diff --git a/third_party/blink/renderer/modules/document_picture_in_picture/picture_in_picture_controller_impl.cc b/third_party/blink/renderer/modules/document_picture_in_picture/picture_in_picture_controller_impl.cc
index 2d9ccb8..41cbb9a 100644
--- a/third_party/blink/renderer/modules/document_picture_in_picture/picture_in_picture_controller_impl.cc
+++ b/third_party/blink/renderer/modules/document_picture_in_picture/picture_in_picture_controller_impl.cc
@@ -367,7 +367,6 @@
   WebPictureInPictureWindowOptions web_options;
   web_options.width = options->width();
   web_options.height = options->height();
-  web_options.initial_aspect_ratio = options->initialAspectRatio();
 
   // If either width or height is specified, then both must be specified.
   if (web_options.width > 0 && web_options.height == 0) {
diff --git a/third_party/blink/renderer/modules/netinfo/network_information.idl b/third_party/blink/renderer/modules/netinfo/network_information.idl
index 533b671..fbbaa07 100644
--- a/third_party/blink/renderer/modules/netinfo/network_information.idl
+++ b/third_party/blink/renderer/modules/netinfo/network_information.idl
@@ -29,12 +29,12 @@
     ActiveScriptWrappable,
     Exposed=(Window,Worker)
 ] interface NetworkInformation : EventTarget {
-    [RuntimeEnabled=NetInfoDownlinkMax, MeasureAs=NetInfoType] readonly attribute ConnectionType type;
-    [RuntimeEnabled=NetInfoDownlinkMax, MeasureAs=NetInfoDownlinkMax] readonly attribute Megabit downlinkMax;
+    [RuntimeEnabled=NetInfoDownlinkMax, HighEntropy=Direct, MeasureAs=NetInfoType] readonly attribute ConnectionType type;
+    [RuntimeEnabled=NetInfoDownlinkMax, HighEntropy=Direct, MeasureAs=NetInfoDownlinkMax] readonly attribute Megabit downlinkMax;
     [MeasureAs=NetInfoOnChange] attribute EventHandler onchange;
     [RuntimeEnabled=NetInfoDownlinkMax, MeasureAs=NetInfoOnTypeChange] attribute EventHandler ontypechange;
-    [MeasureAs=NetInfoEffectiveType] readonly attribute EffectiveConnectionType effectiveType;
-    [MeasureAs=NetInfoRtt] readonly attribute Milliseconds rtt;
-    [MeasureAs=NetInfoDownlink] readonly attribute Megabit downlink;
+    [HighEntropy=Direct, MeasureAs=NetInfoEffectiveType] readonly attribute EffectiveConnectionType effectiveType;
+    [HighEntropy=Direct, MeasureAs=NetInfoRtt] readonly attribute Milliseconds rtt;
+    [HighEntropy=Direct, MeasureAs=NetInfoDownlink] readonly attribute Megabit downlink;
     [HighEntropy=Direct, MeasureAs=NetInfoSaveData] readonly attribute boolean saveData;
 };
diff --git a/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.h b/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.h
index 211fdb6..94d3bd5 100644
--- a/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.h
+++ b/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PRESENTATION_PRESENTATION_CONNECTION_CALLBACKS_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_PRESENTATION_PRESENTATION_CONNECTION_CALLBACKS_H_
 
+#include "base/gtest_prod_util.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/mojom/presentation/presentation.mojom-blink-forward.h"
diff --git a/third_party/blink/renderer/modules/private_attribution/BUILD.gn b/third_party/blink/renderer/modules/private_attribution/BUILD.gn
new file mode 100644
index 0000000..056e1240
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/blink/renderer/modules/modules.gni")
+
+blink_modules_sources("private_attribution") {
+  sources = [
+    "private_attribution.cc",
+    "private_attribution.h",
+    "window_private_attribution.cc",
+    "window_private_attribution.h",
+  ]
+}
diff --git a/third_party/blink/renderer/modules/private_attribution/OWNERS b/third_party/blink/renderer/modules/private_attribution/OWNERS
new file mode 100644
index 0000000..f04130a7
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/OWNERS
@@ -0,0 +1,2 @@
+anthonygarant@chromium.org
+csharrison@chromium.org
diff --git a/third_party/blink/renderer/modules/private_attribution/private_attribution.cc b/third_party/blink/renderer/modules/private_attribution/private_attribution.cc
new file mode 100644
index 0000000..893f8f61
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/private_attribution.cc
@@ -0,0 +1,39 @@
+// 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 "third_party/blink/renderer/modules/private_attribution/private_attribution.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+
+namespace blink {
+
+PrivateAttribution::PrivateAttribution() = default;
+
+// static
+ScriptPromise PrivateAttribution::getEncryptedMatchKey(
+    ScriptState*,
+    String report_collector,
+    PrivateAttributionOptions* options,
+    ExceptionState& exception_state) {
+  exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                    "This function is not implemented.");
+  return ScriptPromise();
+}
+
+// static
+ScriptPromise PrivateAttribution::getHelperNetworks(
+    ScriptState*,
+    ExceptionState& exception_state) {
+  exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                    "This function is not implemented.");
+  return ScriptPromise();
+}
+
+void PrivateAttribution::Trace(Visitor* visitor) const {
+  ScriptWrappable::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/private_attribution/private_attribution.h b/third_party/blink/renderer/modules/private_attribution/private_attribution.h
new file mode 100644
index 0000000..75f0af3
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/private_attribution.h
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PRIVATE_ATTRIBUTION_PRIVATE_ATTRIBUTION_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PRIVATE_ATTRIBUTION_PRIVATE_ATTRIBUTION_H_
+
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+
+namespace blink {
+
+class ExceptionState;
+class PrivateAttributionOptions;
+class ScriptPromise;
+class ScriptState;
+
+// Interoperable Private Attribution (IPA) is a new web platform API for
+// advertising attribution.
+//
+// It proposes two new user-agent APIs: `get_encrypted_match_key` and
+// `get_helper_networks`.
+//
+// The match keys which leave the user agent are always encrypted towards a
+// privacy preserving measurement system, i.e., a distributed multi-party
+// computation (MPC) operated by helper parties who are only trusted to not
+// collude.
+//
+// PrivateAttribution object exposes these APIs to the window and is responsible
+// for accepting the parameters, calling browser functions and returning the
+// results back to the api caller.
+class PrivateAttribution final : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit PrivateAttribution();
+  ~PrivateAttribution() final = default;
+
+  static ScriptPromise getEncryptedMatchKey(ScriptState*,
+                                            String report_collector,
+                                            PrivateAttributionOptions* options,
+                                            ExceptionState& exception_state);
+
+  static ScriptPromise getHelperNetworks(ScriptState*,
+                                         ExceptionState& exception_state);
+
+  void Trace(Visitor*) const override;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PRIVATE_ATTRIBUTION_PRIVATE_ATTRIBUTION_H_
diff --git a/third_party/blink/renderer/modules/private_attribution/private_attribution.idl b/third_party/blink/renderer/modules/private_attribution/private_attribution.idl
new file mode 100644
index 0000000..73c34899
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/private_attribution.idl
@@ -0,0 +1,58 @@
+// 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.
+
+// https://github.com/patcg-individual-drafts/ipa
+
+dictionary PrivateAttributionHelperShare {
+  octet keyId;
+
+  ArrayBuffer encryptedShare;
+};
+
+dictionary PrivateAttributionEncryptedMatchKey {
+  record<DOMString, PrivateAttributionHelperShare> shares;
+};
+
+dictionary PrivateAttributionOptions {
+  PrivateAttributionEvent eventType;
+
+  unsigned long helperNetworkId;
+};
+
+dictionary PrivateAttributionHelperInfo {
+  DOMString origin;
+
+  DOMString publicKey;
+};
+
+[Serializable]
+dictionary PrivateAttributionNetwork {
+  unsigned long id;
+
+  sequence<PrivateAttributionHelperInfo> helpers;
+};
+
+enum PrivateAttributionEvent {
+  "source",
+  "trigger",
+};
+
+[
+  Exposed=(Window),
+  SecureContext,
+  RuntimeEnabled=InteroperablePrivateAttribution
+] interface PrivateAttribution {
+  [
+    CallWith=ScriptState,
+    RaisesException
+  ] Promise<PrivateAttributionEncryptedMatchKey> getEncryptedMatchKey(
+      DOMString reportCollector,
+
+      PrivateAttributionOptions options);
+
+  [
+    CallWith=ScriptState,
+    RaisesException
+  ] Promise<sequence<PrivateAttributionNetwork>> getHelperNetworks();
+};
diff --git a/third_party/blink/renderer/modules/private_attribution/window_private_attribution.cc b/third_party/blink/renderer/modules/private_attribution/window_private_attribution.cc
new file mode 100644
index 0000000..52f8da42
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/window_private_attribution.cc
@@ -0,0 +1,49 @@
+// 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 "third_party/blink/renderer/modules/private_attribution/window_private_attribution.h"
+
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/modules/private_attribution/private_attribution.h"
+
+namespace blink {
+
+WindowPrivateAttribution::WindowPrivateAttribution(LocalDOMWindow& window)
+    : Supplement<LocalDOMWindow>(window) {}
+
+// static
+const char WindowPrivateAttribution::kSupplementName[] =
+    "WindowPrivateAttribution";
+
+// static
+WindowPrivateAttribution& WindowPrivateAttribution::From(
+    LocalDOMWindow& window) {
+  WindowPrivateAttribution* supplement =
+      Supplement<LocalDOMWindow>::From<WindowPrivateAttribution>(window);
+  if (!supplement) {
+    supplement = MakeGarbageCollected<WindowPrivateAttribution>(window);
+    ProvideTo(window, supplement);
+  }
+  return *supplement;
+}
+
+// static
+PrivateAttribution* WindowPrivateAttribution::privateAttribution(
+    LocalDOMWindow& window) {
+  return WindowPrivateAttribution::From(window).privateAttribution();
+}
+
+PrivateAttribution* WindowPrivateAttribution::privateAttribution() {
+  if (!private_attribution_) {
+    private_attribution_ = MakeGarbageCollected<PrivateAttribution>();
+  }
+  return private_attribution_.Get();
+}
+
+void WindowPrivateAttribution::Trace(Visitor* visitor) const {
+  visitor->Trace(private_attribution_);
+  Supplement<LocalDOMWindow>::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/private_attribution/window_private_attribution.h b/third_party/blink/renderer/modules/private_attribution/window_private_attribution.h
new file mode 100644
index 0000000..be2db15
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/window_private_attribution.h
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PRIVATE_ATTRIBUTION_WINDOW_PRIVATE_ATTRIBUTION_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PRIVATE_ATTRIBUTION_WINDOW_PRIVATE_ATTRIBUTION_H_
+
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/supplementable.h"
+
+namespace blink {
+
+class LocalDOMWindow;
+class PrivateAttribution;
+
+// Implement privateAttribution attribute under Window.
+class WindowPrivateAttribution final
+    : public GarbageCollected<WindowPrivateAttribution>,
+      public Supplement<LocalDOMWindow> {
+ public:
+  static const char kSupplementName[];
+
+  static WindowPrivateAttribution& From(LocalDOMWindow&);
+  static PrivateAttribution* privateAttribution(LocalDOMWindow&);
+
+  explicit WindowPrivateAttribution(LocalDOMWindow&);
+
+  PrivateAttribution* privateAttribution();
+
+  void Trace(Visitor*) const override;
+
+ private:
+  Member<PrivateAttribution> private_attribution_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PRIVATE_ATTRIBUTION_WINDOW_PRIVATE_ATTRIBUTION_H_
diff --git a/third_party/blink/renderer/modules/private_attribution/window_private_attribution.idl b/third_party/blink/renderer/modules/private_attribution/window_private_attribution.idl
new file mode 100644
index 0000000..4676f5fd
--- /dev/null
+++ b/third_party/blink/renderer/modules/private_attribution/window_private_attribution.idl
@@ -0,0 +1,14 @@
+// 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.
+
+
+// https://github.com/patcg-individual-drafts/ipa
+
+[
+    ImplementedAs=WindowPrivateAttribution,
+    SecureContext,
+    RuntimeEnabled=InteroperablePrivateAttribution
+] partial interface Window {
+    [SameObject, Replaceable] readonly attribute PrivateAttribution privateAttribution;
+};
diff --git a/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc b/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc
index 3deeb7b9..35e1eba 100644
--- a/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc
+++ b/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc
@@ -25,15 +25,9 @@
 // static
 ServiceWorkerContainer* NavigatorServiceWorker::serviceWorker(
     ScriptState* script_state,
-    Navigator& navigator,
+    Navigator&,
     ExceptionState& exception_state) {
-  if (!navigator.DomWindow())
-    return nullptr;
-  LocalDOMWindow& window = *navigator.DomWindow();
-  DCHECK(ExecutionContext::From(script_state)
-             ->GetSecurityOrigin()
-             ->CanAccess(window.GetSecurityOrigin()));
-
+  LocalDOMWindow& window = *LocalDOMWindow::From(script_state);
   auto* container = From(window);
   if (!container) {
     String error_message;
diff --git a/third_party/blink/renderer/modules/smart_card/smart_card_connection.cc b/third_party/blink/renderer/modules/smart_card/smart_card_connection.cc
index fce373a..2c50db32 100644
--- a/third_party/blink/renderer/modules/smart_card/smart_card_connection.cc
+++ b/third_party/blink/renderer/modules/smart_card/smart_card_connection.cc
@@ -6,6 +6,8 @@
 
 #include "base/notreached.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_smart_card_disposition.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_smart_card_protocol.h"
+#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
 #include "third_party/blink/renderer/modules/smart_card/smart_card_error.h"
 
 namespace blink {
@@ -33,8 +35,9 @@
 SmartCardConnection::SmartCardConnection(
     mojo::PendingRemote<device::mojom::blink::SmartCardConnection>
         pending_connection,
+    device::mojom::blink::SmartCardProtocol active_protocol,
     ExecutionContext* execution_context)
-    : connection_(execution_context) {
+    : connection_(execution_context), active_protocol_(active_protocol) {
   connection_.Bind(
       std::move(pending_connection),
       execution_context->GetTaskRunner(TaskType::kMiscPlatformAPI));
@@ -69,6 +72,36 @@
   return resolver->Promise();
 }
 
+ScriptPromise SmartCardConnection::transmit(ScriptState* script_state,
+                                            const DOMArrayPiece& send_buffer,
+                                            ExceptionState& exception_state) {
+  if (!EnsureNoOperationInProgress(exception_state) ||
+      !EnsureConnection(exception_state)) {
+    return ScriptPromise();
+  }
+
+  if (send_buffer.IsDetached() || send_buffer.IsNull()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Invalid send buffer.");
+    return ScriptPromise();
+  }
+
+  ScriptPromiseResolver* resolver = MakeGarbageCollected<ScriptPromiseResolver>(
+      script_state, exception_state.GetContext());
+
+  Vector<uint8_t> send_vector;
+  send_vector.Append(send_buffer.Bytes(),
+                     static_cast<wtf_size_t>(send_buffer.ByteLength()));
+
+  operation_in_progress_ = true;
+  connection_->Transmit(
+      active_protocol_, send_vector,
+      WTF::BindOnce(&SmartCardConnection::OnDataResult, WrapPersistent(this),
+                    WrapPersistent(resolver)));
+
+  return resolver->Promise();
+}
+
 ScriptPromise SmartCardConnection::status() {
   NOTIMPLEMENTED();
   return ScriptPromise();
@@ -117,4 +150,21 @@
   resolver->Resolve();
 }
 
+void SmartCardConnection::OnDataResult(
+    ScriptPromiseResolver* resolver,
+    device::mojom::blink::SmartCardDataResultPtr result) {
+  CHECK(operation_in_progress_);
+  operation_in_progress_ = false;
+
+  if (result->is_error()) {
+    auto* error = SmartCardError::Create(result->get_error());
+    resolver->Reject(error);
+    return;
+  }
+
+  const Vector<uint8_t>& data = result->get_data();
+
+  resolver->Resolve(DOMArrayBuffer::Create(data.data(), data.size()));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/smart_card/smart_card_connection.h b/third_party/blink/renderer/modules/smart_card/smart_card_connection.h
index 29dcee9..0aa2da0 100644
--- a/third_party/blink/renderer/modules/smart_card/smart_card_connection.h
+++ b/third_party/blink/renderer/modules/smart_card/smart_card_connection.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/typed_arrays/dom_array_piece.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
@@ -23,6 +24,7 @@
  public:
   explicit SmartCardConnection(
       mojo::PendingRemote<device::mojom::blink::SmartCardConnection>,
+      device::mojom::blink::SmartCardProtocol active_protocol,
       ExecutionContext*);
 
   // SmartCardConnection idl
@@ -31,6 +33,9 @@
   ScriptPromise disconnect(ScriptState* script_state,
                            const V8SmartCardDisposition& disposition,
                            ExceptionState& exception_state);
+  ScriptPromise transmit(ScriptState* script_state,
+                         const DOMArrayPiece& send_buffer,
+                         ExceptionState& exception_state);
   ScriptPromise status();
 
   // ScriptWrappable overrides
@@ -41,9 +46,12 @@
   bool EnsureConnection(ExceptionState& exception_state) const;
   void OnDisconnectDone(ScriptPromiseResolver* resolver,
                         device::mojom::blink::SmartCardResultPtr result);
+  void OnDataResult(ScriptPromiseResolver* resolver,
+                    device::mojom::blink::SmartCardDataResultPtr result);
 
   bool operation_in_progress_ = false;
   HeapMojoRemote<device::mojom::blink::SmartCardConnection> connection_;
+  device::mojom::blink::SmartCardProtocol active_protocol_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/smart_card/smart_card_connection.idl b/third_party/blink/renderer/modules/smart_card/smart_card_connection.idl
index c767a64..7e7908b 100644
--- a/third_party/blink/renderer/modules/smart_card/smart_card_connection.idl
+++ b/third_party/blink/renderer/modules/smart_card/smart_card_connection.idl
@@ -20,6 +20,9 @@
   [CallWith=ScriptState, RaisesException]
   Promise<void> disconnect(optional SmartCardDisposition disposition = "leave");
 
+  [CallWith=ScriptState, RaisesException]
+  Promise<ArrayBuffer> transmit(BufferSource sendBuffer);
+
   Promise<SmartCardConnectionStatus> status();
 
   // TODO(crbug.com/1386175): add the rest of the API
diff --git a/third_party/blink/renderer/modules/smart_card/smart_card_reader.cc b/third_party/blink/renderer/modules/smart_card/smart_card_reader.cc
index e5937f4..ba0b4f9 100644
--- a/third_party/blink/renderer/modules/smart_card/smart_card_reader.cc
+++ b/third_party/blink/renderer/modules/smart_card/smart_card_reader.cc
@@ -151,8 +151,12 @@
     return;
   }
 
+  device::mojom::blink::SmartCardConnectSuccessPtr& success =
+      result->get_success();
+
   auto* connection = MakeGarbageCollected<SmartCardConnection>(
-      std::move(result->get_success()->connection), GetExecutionContext());
+      std::move(success->connection), success->active_protocol,
+      GetExecutionContext());
 
   resolver->Resolve(connection);
 }
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.h b/third_party/blink/renderer/modules/webaudio/audio_context.h
index 1a8df37..26f9d22 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_CONTEXT_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_CONTEXT_H_
 
+#include "base/gtest_prod_util.h"
 #include "third_party/blink/public/mojom/mediastream/media_devices.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
 #include "third_party/blink/public/mojom/webaudio/audio_context_manager.mojom-blink.h"
diff --git a/third_party/blink/renderer/platform/audio/push_pull_fifo.cc b/third_party/blink/renderer/platform/audio/push_pull_fifo.cc
index 10d67e1..fc50569 100644
--- a/third_party/blink/renderer/platform/audio/push_pull_fifo.cc
+++ b/third_party/blink/renderer/platform/audio/push_pull_fifo.cc
@@ -104,11 +104,16 @@
                    << ", inputFrames=" << input_bus_length
                    << ", fifoLength=" << fifo_length_ << ")";
     }
+    TRACE_EVENT_INSTANT2("webaudio", "PushPullFIFO overrun",
+                         TRACE_EVENT_SCOPE_THREAD, "extra frames",
+                         input_bus_length + frames_available_ - fifo_length_,
+                         "overflow_count_", overflow_count_);
   }
 
   // Update the number of frames available in FIFO.
   frames_available_ =
       std::min(frames_available_ + input_bus_length, fifo_length_);
+  TRACE_COUNTER_ID1("webaudio", "PushPullFIFO frames", this, frames_available_);
   DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
 }
 
@@ -178,8 +183,6 @@
     // The frames available was not enough to fulfill the requested frames. Fill
     // the rest of the channel with silence.
     if (frames_requested > frames_to_fill) {
-      TRACE_EVENT1("webaudio", "PushPullFIFO::Pull underrun", "missing frames",
-                   frames_requested - frames_to_fill);
       memset(output_bus_channel + frames_to_fill, 0,
              (frames_requested - frames_to_fill) * sizeof(*output_bus_channel));
     }
@@ -198,10 +201,16 @@
                    << ", requestedFrames=" << frames_requested
                    << ", fifoLength=" << fifo_length_ << ")";
     }
+    TRACE_EVENT_INSTANT2("webaudio", "PushPullFIFO::Pull underrun",
+                         TRACE_EVENT_SCOPE_THREAD, "missing frames",
+                         frames_requested - frames_to_fill, "underflow_count_",
+                         underflow_count_);
   }
 
   // Update the number of frames in FIFO.
   frames_available_ -= frames_to_fill;
+  TRACE_COUNTER_ID1("webaudio", "PushPullFIFO frames", this, frames_available_);
+
   DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
 
   pull_count_++;
@@ -242,10 +251,10 @@
                    << ", fifoLength=" << fifo_length_ << ")";
     }
 
-    TRACE_EVENT2("webaudio",
-                 "PushPullFIFO::PullAndUpdateEarmark (underrun)",
-                 "missing frames", missing_frames,
-                 "underflow_count_", underflow_count_);
+    TRACE_EVENT_INSTANT2("webaudio",
+                         "PushPullFIFO::PullAndUpdateEarmark underrun",
+                         TRACE_EVENT_SCOPE_THREAD, "missing frames",
+                         missing_frames, "underflow_count_", underflow_count_);
 
     // We assume that the next |frames_requested| from |AudioOutputDevice| will
     // be the same.
@@ -297,6 +306,7 @@
   // Update the number of frames in FIFO.
   frames_available_ -= frames_to_fill;
   DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
+  TRACE_COUNTER_ID1("webaudio", "PushPullFIFO frames", this, frames_available_);
 
   pull_count_++;
 
diff --git a/third_party/blink/renderer/platform/fonts/DEPS b/third_party/blink/renderer/platform/fonts/DEPS
index 9bdc916..5a47121c 100644
--- a/third_party/blink/renderer/platform/fonts/DEPS
+++ b/third_party/blink/renderer/platform/fonts/DEPS
@@ -6,6 +6,7 @@
     "+third_party/blink/renderer/platform/fonts",
 
     # Dependencies.
+    "+base/apple",
     "+base/mac",
     "+base/win",
     "+cc",
diff --git a/third_party/blink/renderer/platform/fonts/mac/attributed_string_type_converter.mm b/third_party/blink/renderer/platform/fonts/mac/attributed_string_type_converter.mm
index 376488b..366520a 100644
--- a/third_party/blink/renderer/platform/fonts/mac/attributed_string_type_converter.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/attributed_string_type_converter.mm
@@ -6,7 +6,7 @@
 
 #include <AppKit/AppKit.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "ui/gfx/range/range.h"
@@ -21,7 +21,7 @@
 TypeConverter<ui::mojom::blink::AttributedStringPtr, CFAttributedStringRef>::
     Convert(CFAttributedStringRef cf_attributed_string) {
   NSAttributedString* ns_attributed_string =
-      base::mac::CFToNSPtrCast(cf_attributed_string);
+      base::apple::CFToNSPtrCast(cf_attributed_string);
 
   // Create the return value.
   ui::mojom::blink::AttributedStringPtr attributed_string =
diff --git a/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm b/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm
index 6eb4e183..5337789 100644
--- a/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/font_cache_mac.mm
@@ -34,8 +34,8 @@
 #import <AppKit/AppKit.h>
 #import <CoreText/CoreText.h>
 
+#include "base/apple/bridging.h"
 #include "base/location.h"
-#include "base/mac/bridging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/timer/elapsed_timer.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -146,7 +146,7 @@
 
   const FontPlatformData& platform_data =
       font_data_to_substitute->PlatformData();
-  NSFont* ns_font = base::mac::CFToNSPtrCast(platform_data.CtFont());
+  NSFont* ns_font = base::apple::CFToNSPtrCast(platform_data.CtFont());
 
   NSString* string = [[NSString alloc]
       initWithCharacters:reinterpret_cast<UniChar*>(code_units)
diff --git a/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm b/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm
index 5164c07..cc02e98 100644
--- a/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm
@@ -33,7 +33,7 @@
 #import <Foundation/Foundation.h>
 #import <math.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
@@ -127,12 +127,12 @@
   // searching for fonts with its value as their display name.
   NSString* desired_name = unique_font_name;
   NSDictionary* attributes = @{
-    base::mac::CFToNSPtrCast(kCTFontNameAttribute) : desired_name,
-    base::mac::CFToNSPtrCast(kCTFontSizeAttribute) : @(size)
+    base::apple::CFToNSPtrCast(kCTFontNameAttribute) : desired_name,
+    base::apple::CFToNSPtrCast(kCTFontSizeAttribute) : @(size)
   };
   base::ScopedCFTypeRef<CTFontDescriptorRef> descriptor(
       CTFontDescriptorCreateWithAttributes(
-          base::mac::NSToCFPtrCast(attributes)));
+          base::apple::NSToCFPtrCast(attributes)));
 
   base::ScopedCFTypeRef<CTFontRef> matched_font(
       CTFontCreateWithFontDescriptor(descriptor, 0, nullptr));
@@ -140,9 +140,9 @@
 
   // CoreText will usually give us *something* but not always an exactly matched
   // font.
-  NSString* matched_postscript_name = base::mac::CFToNSOwnershipCast(
+  NSString* matched_postscript_name = base::apple::CFToNSOwnershipCast(
       CTFontCopyName(matched_font, kCTFontPostScriptNameKey));
-  NSString* matched_full_font_name = base::mac::CFToNSOwnershipCast(
+  NSString* matched_full_font_name = base::apple::CFToNSOwnershipCast(
       CTFontCopyName(matched_font, kCTFontFullNameKey));
   // If the found font does not match in PostScript name or full font name, it's
   // not the exact match that is required, so return nullptr.
@@ -153,7 +153,7 @@
     return nullptr;
   }
 
-  return base::mac::CFToNSOwnershipCast(matched_font.release());
+  return base::apple::CFToNSOwnershipCast(matched_font.release());
 }
 
 // Family name is somewhat of a misnomer here.  We first attempt to find an
diff --git a/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac.mm b/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac.mm
index 1bf85eaa..e67ca7a 100644
--- a/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac.mm
@@ -26,7 +26,7 @@
 #import <AppKit/AppKit.h>
 #import <AvailabilityMacros.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #import "base/mac/foundation_util.h"
 #include "third_party/blink/renderer/platform/fonts/font.h"
 #include "third_party/blink/renderer/platform/fonts/font_platform_data.h"
@@ -98,9 +98,9 @@
 
 static bool CanLoadInProcess(NSFont* ns_font) {
   base::ScopedCFTypeRef<CGFontRef> cg_font(CTFontCopyGraphicsFont(
-      base::mac::NSToCFPtrCast(ns_font), /*attributes=*/nullptr));
+      base::apple::NSToCFPtrCast(ns_font), /*attributes=*/nullptr));
   NSString* font_name =
-      base::mac::CFToNSOwnershipCast(CGFontCopyPostScriptName(cg_font));
+      base::apple::CFToNSOwnershipCast(CGFontCopyPostScriptName(cg_font));
   return ![font_name isEqualToString:@"LastResort"];
 }
 
@@ -122,7 +122,7 @@
   DCHECK(CanLoadInProcess(ns_font));
 
   sk_sp<SkTypeface> typeface =
-      SkMakeTypefaceFromCTFont(base::mac::NSToCFPtrCast(ns_font));
+      SkMakeTypefaceFromCTFont(base::apple::NSToCFPtrCast(ns_font));
 
   auto make_typeface_fontplatformdata = [&typeface, &size, &synthetic_bold,
                                          &synthetic_italic, &text_rendering,
diff --git a/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac_test.mm b/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac_test.mm
index 07acae4..38ac07d 100644
--- a/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac_test.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac_test.mm
@@ -4,7 +4,7 @@
 
 #include "third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac.h"
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/font_family_names.h"
@@ -23,7 +23,7 @@
 constexpr SkFourByteTag kWghtTag = SkSetFourByteTag('w', 'g', 'h', 't');
 
 sk_sp<SkTypeface> MakeSystemFontOfSize(float size) {
-  return SkMakeTypefaceFromCTFont(base::mac::NSToCFPtrCast(MatchNSFontFamily(
+  return SkMakeTypefaceFromCTFont(base::apple::NSToCFPtrCast(MatchNSFontFamily(
       font_family_names::kSystemUi, 0, FontSelectionValue(400), size)));
 }
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 793f7ac..3d527f87 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -102,8 +102,7 @@
   if (lcd_text_preference_ == preference) {
     return;
   }
-  SetNeedsUpdate(PaintArtifactCompositorUpdateReason::
-                     kPaintArtifactCompositorPrefersLCDText);
+  SetNeedsUpdate();
   lcd_text_preference_ = preference;
 }
 
@@ -391,8 +390,7 @@
 
   // Adding or removing chunks requires a full update to add/remove cc::layers.
   if (previous.PaintChunks().size() != repainted.PaintChunks().size()) {
-    SetNeedsUpdate(PaintArtifactCompositorUpdateReason::
-                       kPaintArtifactCompositorNeedsFullUpdateChunksChanged);
+    SetNeedsUpdate();
     return;
   }
 
@@ -401,9 +399,7 @@
     if (NeedsFullUpdateAfterPaintingChunk(previous.PaintChunks()[i], previous,
                                           repainted.PaintChunks()[i],
                                           repainted)) {
-      SetNeedsUpdate(
-          PaintArtifactCompositorUpdateReason::
-              kPaintArtifactCompositorNeedsFullUpdateAfterPaintingChunk);
+      SetNeedsUpdate();
       return;
     }
   }
@@ -1384,17 +1380,4 @@
   return nullptr;
 }
 
-void PaintArtifactCompositor::SetNeedsUpdate(
-    PaintArtifactCompositorUpdateReason reason) {
-  UMA_HISTOGRAM_ENUMERATION("Blink.Paint.PaintArtifactCompositorUpdateReason",
-                            reason,
-                            PaintArtifactCompositorUpdateReason::kCount);
-  if (!needs_update_) {
-    needs_update_ = true;
-    UMA_HISTOGRAM_ENUMERATION(
-        "Blink.Paint.PaintArtifactCompositorUpdateFirstReason", reason,
-        PaintArtifactCompositorUpdateReason::kCount);
-  }
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index f3577229..8683b39 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -42,34 +42,6 @@
 
 using CompositorScrollCallbacks = cc::ScrollCallbacks;
 
-// This enum is used for histograms and should not be renumbered (see:
-// PaintArtifactCompositorUpdateReason in tools/metrics/histograms/enums.xml).
-enum class PaintArtifactCompositorUpdateReason {
-  kTest = 0,
-  kPaintArtifactCompositorNeedsFullUpdateChunksChanged = 1,
-  kPaintArtifactCompositorNeedsFullUpdateAfterPaintingChunk = 2,
-  kPaintArtifactCompositorPrefersLCDText = 3,
-  kLocalFrameViewUpdateLayerDebugInfo = 4,
-  kLocalFrameViewBenchmarking = 5,
-  kDisplayLockContextNeedsPaintArtifactCompositorUpdate = 6,
-  kViewTransitionNotifyChanges = 7,
-  kFrameCaretSetVisible = 8,
-  kFrameCaretPaint = 9,
-  kInspectorOverlayAgentDisableFrameOverlay = 10,
-  kLinkHighlightImplNeedsCompositingUpdate = 11,
-  kPaintLayerScrollableAreaUpdateScrollOffset = 12,
-  kPaintPropertyTreeBuilderPaintPropertyChanged = 13,
-  kPaintPropertyTreeBuilderHasFixedPositionObjects = 14,
-  kPaintPropertyTreeBulderNonStackingContextScroll = 15,
-  kVisualViewportPaintPropertyTreeBuilderUpdate = 16,
-  kVideoPainterPaintReplaced = 17,
-  kPaintPropertyTreeBuilderPaintPropertyChangedOnlyNonRerasterValues = 18,
-  kPaintPropertyTreeBuilderPaintPropertyChangedOnlySimpleValues = 19,
-  kPaintPropertyTreeBuilderPaintPropertyChangedOnlyValues = 20,
-  kPaintPropertyTreeBuilderPaintPropertyAddedOrRemoved = 21,
-  kCount = 22
-};
-
 class LayerListBuilder {
  public:
   void Add(scoped_refptr<cc::Layer>);
@@ -233,7 +205,7 @@
   // do not affect compositing can use a fast-path in |UpdateRepaintedLayers|
   // (see comment above that function for more information), and should not call
   // SetNeedsUpdate.
-  void SetNeedsUpdate(PaintArtifactCompositorUpdateReason reason);
+  void SetNeedsUpdate() { needs_update_ = true; }
   bool NeedsUpdate() const { return needs_update_; }
   void ClearNeedsUpdateForTesting() { needs_update_ = false; }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index 3a50fdc..20ebcc8 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -151,8 +151,7 @@
       const ViewportProperties& viewport_properties = ViewportProperties(),
       const WTF::Vector<const TransformPaintPropertyNode*>&
           scroll_translation_nodes = {}) {
-    paint_artifact_compositor_->SetNeedsUpdate(
-        PaintArtifactCompositorUpdateReason::kTest);
+    paint_artifact_compositor_->SetNeedsUpdate();
     paint_artifact_compositor_->Update(artifact, viewport_properties,
                                        scroll_translation_nodes, {}, {});
     layer_tree_->layer_tree_host()->LayoutAndUpdateLayers();
diff --git a/third_party/blink/renderer/platform/graphics/crossfade_generated_image.cc b/third_party/blink/renderer/platform/graphics/crossfade_generated_image.cc
index a1a0e2f..d584fc07 100644
--- a/third_party/blink/renderer/platform/graphics/crossfade_generated_image.cc
+++ b/third_party/blink/renderer/platform/graphics/crossfade_generated_image.cc
@@ -91,19 +91,15 @@
   DrawCrossfade(canvas, flags, draw_options);
 }
 
-void CrossfadeGeneratedImage::DrawTile(GraphicsContext& context,
+void CrossfadeGeneratedImage::DrawTile(cc::PaintCanvas* canvas,
                                        const gfx::RectF& src_rect,
                                        const ImageDrawOptions& options) {
   // Draw nothing if either of the images hasn't loaded yet.
   if (from_image_ == Image::NullImage() || to_image_ == Image::NullImage())
     return;
-  cc::PaintFlags flags = context.FillFlags();
-  flags.setBlendMode(SkBlendMode::kSrcOver);
-  gfx::RectF dest_rect(size_);
-  ImageDrawOptions draw_options(options);
-  draw_options.sampling_options =
-      context.ComputeSamplingOptions(*this, dest_rect, src_rect);
-  DrawCrossfade(context.Canvas(), flags, draw_options);
+  cc::PaintFlags flags;
+  flags.setAntiAlias(true);
+  DrawCrossfade(canvas, flags, options);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/crossfade_generated_image.h b/third_party/blink/renderer/platform/graphics/crossfade_generated_image.h
index f4c4d04..175ad9c 100644
--- a/third_party/blink/renderer/platform/graphics/crossfade_generated_image.h
+++ b/third_party/blink/renderer/platform/graphics/crossfade_generated_image.h
@@ -57,7 +57,7 @@
             const gfx::RectF&,
             const gfx::RectF&,
             const ImageDrawOptions& draw_options) override;
-  void DrawTile(GraphicsContext&,
+  void DrawTile(cc::PaintCanvas*,
                 const gfx::RectF&,
                 const ImageDrawOptions&) final;
 
diff --git a/third_party/blink/renderer/platform/graphics/generated_image.cc b/third_party/blink/renderer/platform/graphics/generated_image.cc
index 81c98626ae..9a876be 100644
--- a/third_party/blink/renderer/platform/graphics/generated_image.cc
+++ b/third_party/blink/renderer/platform/graphics/generated_image.cc
@@ -31,9 +31,9 @@
 #include "third_party/blink/renderer/platform/graphics/generated_image.h"
 
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
-#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_shader.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/skia_conversions.h"
@@ -44,7 +44,7 @@
                                  const cc::PaintFlags& base_flags,
                                  const gfx::RectF& dest_rect,
                                  const ImageTilingInfo& tiling_info,
-                                 const ImageDrawOptions& draw_options) {
+                                 const ImageDrawOptions& options) {
   gfx::RectF tile_rect = tiling_info.image_rect;
   tile_rect.set_size(tile_rect.size() + tiling_info.spacing);
 
@@ -53,6 +53,13 @@
   pattern_matrix.preScale(tiling_info.scale.x(), tiling_info.scale.y());
   pattern_matrix.preTranslate(tile_rect.x(), tile_rect.y());
 
+  ImageDrawOptions draw_options(options);
+  // TODO(fs): Computing sampling options using `size_` and the tile source
+  // rect doesn't seem all too useful since they should be in the same space.
+  // Should probably be using the tile source mapped to destination space
+  // (instead of `size_`).
+  draw_options.sampling_options = dest_context.ComputeSamplingOptions(
+      *this, gfx::RectF(size_), tiling_info.image_rect);
   sk_sp<PaintShader> tile_shader = CreateShader(
       tile_rect, &pattern_matrix, tiling_info.image_rect, draw_options);
 
@@ -69,16 +76,11 @@
     const SkMatrix* pattern_matrix,
     const gfx::RectF& src_rect,
     const ImageDrawOptions& draw_options) {
-  auto paint_controller =
-      std::make_unique<PaintController>(PaintController::kTransient);
-  GraphicsContext context(*paint_controller);
-  context.BeginRecording();
-  DrawTile(context, src_rect, draw_options);
-  PaintRecord record = context.EndRecording();
-
+  PaintRecorder recorder;
+  DrawTile(recorder.beginRecording(), src_rect, draw_options);
   return PaintShader::MakePaintRecord(
-      std::move(record), gfx::RectFToSkRect(tile_rect), SkTileMode::kRepeat,
-      SkTileMode::kRepeat, pattern_matrix);
+      recorder.finishRecordingAsPicture(), gfx::RectFToSkRect(tile_rect),
+      SkTileMode::kRepeat, SkTileMode::kRepeat, pattern_matrix);
 }
 
 PaintImage GeneratedImage::PaintImageForCurrentFrame() {
diff --git a/third_party/blink/renderer/platform/graphics/generated_image.h b/third_party/blink/renderer/platform/graphics/generated_image.h
index 2dbb9089..d0377fd 100644
--- a/third_party/blink/renderer/platform/graphics/generated_image.h
+++ b/third_party/blink/renderer/platform/graphics/generated_image.h
@@ -55,6 +55,16 @@
                    const gfx::RectF& dest_rect,
                    const ImageTilingInfo&,
                    const ImageDrawOptions& draw_options) final;
+
+  // Implementation hook for the `DrawPattern()` implementation. `tile_rect` is
+  // a single tile rectangle including any spacing. `pattern_matrix` contains
+  // the transform from tile space to destination space. `src_rect` is the
+  // rectangle containing actual content (`tile_rect` minus any spacing).
+  //
+  // Provide an implementation of this for a subclass if it can generate a more
+  // efficient PaintShader than the default PaintRecord-based shader. If this
+  // is overridden, then the `DrawTile()` implementation can be empty since it
+  // won't be used.
   virtual sk_sp<cc::PaintShader> CreateShader(
       const gfx::RectF& tile_rect,
       const SkMatrix* pattern_matrix,
@@ -66,7 +76,10 @@
 
   GeneratedImage(const gfx::SizeF& size) : size_(size) {}
 
-  virtual void DrawTile(GraphicsContext&,
+  // Implementation hook for `CreateShader()`. Is passed a source rectangle
+  // (see `CreateShader()` above) that should be painted onto the provided
+  // PaintCanvas.
+  virtual void DrawTile(cc::PaintCanvas*,
                         const gfx::RectF&,
                         const ImageDrawOptions&) = 0;
 
diff --git a/third_party/blink/renderer/platform/graphics/gradient_generated_image.cc b/third_party/blink/renderer/platform/graphics/gradient_generated_image.cc
index 6f839ad..701ff5a4 100644
--- a/third_party/blink/renderer/platform/graphics/gradient_generated_image.cc
+++ b/third_party/blink/renderer/platform/graphics/gradient_generated_image.cc
@@ -50,16 +50,13 @@
   canvas->drawRect(visible_dest_rect, gradient_flags);
 }
 
-void GradientGeneratedImage::DrawTile(GraphicsContext& context,
+void GradientGeneratedImage::DrawTile(cc::PaintCanvas* canvas,
                                       const gfx::RectF& src_rect,
                                       const ImageDrawOptions& draw_options) {
-  // TODO(ccameron): This function should not ignore |context|'s color behavior.
-  // https://crbug.com/672306
-  cc::PaintFlags gradient_flags(context.FillFlags());
+  cc::PaintFlags gradient_flags;
+  gradient_flags.setAntiAlias(true);
   gradient_->ApplyToFlags(gradient_flags, SkMatrix::I(), draw_options);
-
-  context.DrawRect(gfx::RectFToSkRect(src_rect), gradient_flags,
-                   AutoDarkMode::Disabled());
+  canvas->drawRect(gfx::RectFToSkRect(src_rect), gradient_flags);
 }
 
 bool GradientGeneratedImage::ApplyShader(cc::PaintFlags& flags,
@@ -68,7 +65,6 @@
                                          const ImageDrawOptions& draw_options) {
   DCHECK(gradient_);
   gradient_->ApplyToFlags(flags, local_matrix, draw_options);
-
   return true;
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/gradient_generated_image.h b/third_party/blink/renderer/platform/graphics/gradient_generated_image.h
index a0ed35c7..ac5ff720 100644
--- a/third_party/blink/renderer/platform/graphics/gradient_generated_image.h
+++ b/third_party/blink/renderer/platform/graphics/gradient_generated_image.h
@@ -54,7 +54,7 @@
             const gfx::RectF& dest_rect,
             const gfx::RectF& src_rect,
             const ImageDrawOptions&) override;
-  void DrawTile(GraphicsContext&,
+  void DrawTile(cc::PaintCanvas*,
                 const gfx::RectF&,
                 const ImageDrawOptions& draw_options) override;
 
diff --git a/third_party/blink/renderer/platform/graphics/paint_generated_image.cc b/third_party/blink/renderer/platform/graphics/paint_generated_image.cc
index cb746c0..d2a2424a 100644
--- a/third_party/blink/renderer/platform/graphics/paint_generated_image.cc
+++ b/third_party/blink/renderer/platform/graphics/paint_generated_image.cc
@@ -25,10 +25,10 @@
   canvas->drawPicture(record_);
 }
 
-void PaintGeneratedImage::DrawTile(GraphicsContext& context,
+void PaintGeneratedImage::DrawTile(cc::PaintCanvas* canvas,
                                    const gfx::RectF& src_rect,
                                    const ImageDrawOptions&) {
-  context.DrawRecord(record_);
+  canvas->drawPicture(record_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint_generated_image.h b/third_party/blink/renderer/platform/graphics/paint_generated_image.h
index 56add49..0f6b0a0 100644
--- a/third_party/blink/renderer/platform/graphics/paint_generated_image.h
+++ b/third_party/blink/renderer/platform/graphics/paint_generated_image.h
@@ -24,7 +24,7 @@
             const gfx::RectF&,
             const gfx::RectF&,
             const ImageDrawOptions& draw_options) override;
-  void DrawTile(GraphicsContext&,
+  void DrawTile(cc::PaintCanvas*,
                 const gfx::RectF&,
                 const ImageDrawOptions&) final;
 
diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc b/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc
index 42da378..1b540ec3 100644
--- a/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc
+++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc
@@ -156,6 +156,7 @@
     DLOG(ERROR) << "Couldn't read pixels from PaintImage";
     return;
   }
+  temp_argb_frame->set_color_space(gfx::ColorSpace::CreateSRGB());
   std::move(callback).Run(std::move(temp_argb_frame));
 }
 
@@ -257,6 +258,7 @@
     ReadARGBPixelsSync(image, std::move(callback));
     return;
   }
+  argb_frame->set_color_space(gfx::ColorSpace::CreateSRGB());
   std::move(callback).Run(std::move(argb_frame));
 }
 
@@ -270,6 +272,7 @@
     DLOG(ERROR) << "Couldn't read SkImage using async callback";
     return;
   }
+  yuv_frame->set_color_space(gfx::ColorSpace::CreateREC709());
   std::move(callback).Run(yuv_frame);
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/memory_cache.h b/third_party/blink/renderer/platform/loader/fetch/memory_cache.h
index 1475ca49..90a61b2 100644
--- a/third_party/blink/renderer/platform/loader/fetch/memory_cache.h
+++ b/third_party/blink/renderer/platform/loader/fetch/memory_cache.h
@@ -26,6 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_MEMORY_CACHE_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_MEMORY_CACHE_H_
 
+#include "base/gtest_prod_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index bc82414..250f6c8 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -742,7 +742,6 @@
       // to those properties.
       name: "CSSAnimationDelayStartEnd",
       depends_on: ["ScrollTimeline"],
-      status: "test",
     },
     {
       // Whether <image> values are allowed as counter style <symbol>
@@ -1565,7 +1564,7 @@
       // This only exists so we can use RuntimeEnabled in the IDL file
       // when either implied_by flag is enabled.
       name: "FedCmIdentityProviderInterface",
-      implied_by: ["FedCmIdpSigninStatus", "FedCmUserInfo", "FedCmIdPRegistration"],
+      implied_by: ["FedCmIdpSigninStatus", "FedCmUserInfo", "FedCmIdPRegistration", "FedCmAuthz"],
       base_feature: "none",
     },
     {
@@ -2007,6 +2006,10 @@
       base_feature: "none",
     },
     {
+      name: "InteroperablePrivateAttribution",
+      status: "experimental",
+    },
+    {
       name: "KeyboardAccessibleTooltip",
       status: "experimental",
       base_feature: "none",
@@ -2323,7 +2326,7 @@
     },
     {
       name: "NewFlexboxSizing",
-      status: "test",
+      status: "stable",
     },
     {
       name: "NodeAsNSResolver",
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
index d8c5545..35ed253e 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
@@ -965,9 +965,7 @@
   }
 
   if (task_queue->GetPrioritisationType() ==
-          MainThreadTaskQueue::QueueTraits::PrioritisationType::kInput &&
-      base::FeatureList::IsEnabled(
-          ::blink::features::kInputTargetClientHighPriority)) {
+      MainThreadTaskQueue::QueueTraits::PrioritisationType::kInput) {
     return TaskPriority::kHighestPriority;
   }
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 69757afc..5b1f0e0 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -1438,27 +1438,9 @@
       testing::UnorderedElementsAre(base::Bucket(1, 1), base::Bucket(2, 1)));
 }
 
-class InputHighPriorityFrameSchedulerImplTest : public FrameSchedulerImplTest {
- public:
-  InputHighPriorityFrameSchedulerImplTest()
-      : FrameSchedulerImplTest(
-            {},
-            {::blink::features::kInputTargetClientHighPriority}) {}
-};
-
 // TODO(farahcharab) Move priority testing to MainThreadTaskQueueTest after
 // landing the change that moves priority computation to MainThreadTaskQueue.
 
-TEST_F(InputHighPriorityFrameSchedulerImplTest,
-       NormalPriorityInputBlockingTaskQueue) {
-  page_scheduler_->SetPageVisible(false);
-  EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
-            TaskPriority::kNormalPriority);
-  page_scheduler_->SetPageVisible(true);
-  EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
-            TaskPriority::kNormalPriority);
-}
-
 TEST_F(FrameSchedulerImplTest, HighestPriorityInputBlockingTaskQueue) {
   page_scheduler_->SetPageVisible(false);
   EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
diff --git a/third_party/blink/renderer/platform/text/DEPS b/third_party/blink/renderer/platform/text/DEPS
index 8cfc5a4..602cedc 100644
--- a/third_party/blink/renderer/platform/text/DEPS
+++ b/third_party/blink/renderer/platform/text/DEPS
@@ -6,6 +6,7 @@
     "+third_party/blink/renderer/platform/text",
 
     # Dependencies.
+    "+base/apple",
     "+base/i18n/rtl.h",
     "+base/mac",
     "+third_party/blink/renderer/platform/text/date_components.h",
diff --git a/third_party/blink/renderer/platform/text/hyphenation.h b/third_party/blink/renderer/platform/text/hyphenation.h
index afb2721..3e68577 100644
--- a/third_party/blink/renderer/platform/text/hyphenation.h
+++ b/third_party/blink/renderer/platform/text/hyphenation.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_HYPHENATION_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_HYPHENATION_H_
 
+#include "base/gtest_prod_util.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
diff --git a/third_party/blink/renderer/platform/wtf/DEPS b/third_party/blink/renderer/platform/wtf/DEPS
index 997c15c..f3e09b7 100644
--- a/third_party/blink/renderer/platform/wtf/DEPS
+++ b/third_party/blink/renderer/platform/wtf/DEPS
@@ -1,13 +1,13 @@
 include_rules = [
     # To only allow a subset of base/ in Blink, we explicitly list all
     # directories and files instead of writing 'base/'.
+    "+base/apple/bridging.h",
     "+base/atomic_ref_count.h",
     "+base/auto_reset.h",
     "+base/bits.h",
     "+base/compiler_specific.h",
     "+base/functional/bind.h",
     "+base/logging.h",
-    "+base/mac/bridging.h",
     "+base/mac/foundation_util.h",
     "+base/mac/scoped_cftyperef.h",
     "+base/memory/ptr_util.h",
diff --git a/third_party/blink/renderer/platform/wtf/allocator/partitions.cc b/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
index a0512c4..09c2949 100644
--- a/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
+++ b/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
@@ -97,7 +97,6 @@
            base::features::BackupRefPtrMode::kEnabledWithMemoryReclaimer) &&
       process_affected_by_brp_flag;
 #else  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
-  const bool process_affected_by_brp_flag = false;
   const bool enable_brp = false;
 #endif
   const auto brp_setting =
@@ -107,12 +106,6 @@
       enable_brp && brp_mode == base::features::BackupRefPtrMode::kEnabled
           ? partition_alloc::PartitionOptions::BackupRefPtrZapping::kEnabled
           : partition_alloc::PartitionOptions::BackupRefPtrZapping::kDisabled;
-  const auto add_dummy_ref_count_setting =
-      process_affected_by_brp_flag &&
-              brp_mode ==
-                  base::features::BackupRefPtrMode::kDisabledButAddDummyRefCount
-          ? partition_alloc::PartitionOptions::AddDummyRefCount::kEnabled
-          : partition_alloc::PartitionOptions::AddDummyRefCount::kDisabled;
   scan_is_enabled_ =
       !enable_brp &&
 #if BUILDFLAG(USE_STARSCAN)
@@ -149,7 +142,6 @@
         brp_setting,
         brp_zapping_setting,
         partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
-        add_dummy_ref_count_setting,
     });
     fast_malloc_root_ = fast_malloc_allocator->root();
   }
@@ -166,7 +158,6 @@
       brp_setting,
       brp_zapping_setting,
       partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
-      add_dummy_ref_count_setting,
   });
   buffer_root_ = buffer_allocator->root();
 
diff --git a/third_party/blink/renderer/platform/wtf/text/atomic_string.h b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
index 6d36f60..4e2166d 100644
--- a/third_party/blink/renderer/platform/wtf/text/atomic_string.h
+++ b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
@@ -37,7 +37,7 @@
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 
 #ifdef __OBJC__
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 #endif
 
 namespace WTF {
@@ -194,7 +194,7 @@
   unsigned Hash() const { return string_.Impl()->ExistingHash(); }
 
 #ifdef __OBJC__
-  AtomicString(NSString* s) : string_(Add(base::mac::NSToCFPtrCast(s))) {}
+  AtomicString(NSString* s) : string_(Add(base::apple::NSToCFPtrCast(s))) {}
   operator NSString*() const { return string_; }
 #endif
   // AtomicString::fromUTF8 will return a null string if
diff --git a/third_party/blink/renderer/platform/wtf/text/string_apple.mm b/third_party/blink/renderer/platform/wtf/text/string_apple.mm
index d66d514..ed7cb18 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_apple.mm
+++ b/third_party/blink/renderer/platform/wtf/text/string_apple.mm
@@ -22,7 +22,7 @@
 
 #include <CoreFoundation/CoreFoundation.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -35,7 +35,7 @@
     return;
   }
 
-  CFStringRef cf_str = base::mac::NSToCFPtrCast(str);
+  CFStringRef cf_str = base::apple::NSToCFPtrCast(str);
 
   CFIndex size = CFStringGetLength(cf_str);
   if (size == 0) {
diff --git a/third_party/blink/renderer/platform/wtf/text/string_impl_apple.mm b/third_party/blink/renderer/platform/wtf/text/string_impl_apple.mm
index 142862f2..f78ca04 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_impl_apple.mm
+++ b/third_party/blink/renderer/platform/wtf/text/string_impl_apple.mm
@@ -22,7 +22,7 @@
 
 #import <Foundation/Foundation.h>
 
-#include "base/mac/bridging.h"
+#include "base/apple/bridging.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -43,7 +43,7 @@
 }
 
 StringImpl::operator NSString*() {
-  return base::mac::CFToNSOwnershipCast(CreateCFString().release());
+  return base::apple::CFToNSOwnershipCast(CreateCFString().release());
 }
 
 }  // namespace WTF
diff --git a/third_party/blink/renderer/platform/wtf/wtf.h b/third_party/blink/renderer/platform/wtf/wtf.h
index ba7d18d..f5feece 100644
--- a/third_party/blink/renderer/platform/wtf/wtf.h
+++ b/third_party/blink/renderer/platform/wtf/wtf.h
@@ -50,7 +50,7 @@
 #if BUILDFLAG(IS_ANDROID) || (defined(COMPONENT_BUILD) && BUILDFLAG(IS_WIN))
 WTF_EXPORT bool IsMainThread();
 #else
-WTF_EXPORT extern ABSL_CONST_INIT thread_local bool g_is_main_thread;
+WTF_EXPORT ABSL_CONST_INIT extern thread_local bool g_is_main_thread;
 inline bool IsMainThread() {
   return g_is_main_thread;
 }
diff --git a/third_party/blink/tools/blinkpy/common/system/platform_info_mock.py b/third_party/blink/tools/blinkpy/common/system/platform_info_mock.py
index a9bf83e..dd1d318 100644
--- a/third_party/blink/tools/blinkpy/common/system/platform_info_mock.py
+++ b/third_party/blink/tools/blinkpy/common/system/platform_info_mock.py
@@ -30,7 +30,7 @@
 class MockPlatformInfo(object):
     def __init__(self,
                  os_name='mac',
-                 os_version='mac10.14',
+                 os_version='mac11',
                  linux_distribution=None,
                  is_highdpi=False,
                  is_running_rosetta=False,
diff --git a/third_party/blink/tools/blinkpy/common/wpt_results_diff.py b/third_party/blink/tools/blinkpy/common/wpt_results_diff.py
index c22ecaa..f93a5c5 100644
--- a/third_party/blink/tools/blinkpy/common/wpt_results_diff.py
+++ b/third_party/blink/tools/blinkpy/common/wpt_results_diff.py
@@ -131,7 +131,7 @@
                 expected_result = "PASS"
                 if expected_node.get_subtest(
                         expected_subtest) and expected_node.get_subtest(
-                            expected_subtest).expected:
+                            expected_subtest).has_key("expected"):
                     expected_result = expected_node.get_subtest(
                         expected_subtest).expected
                 self.build_tbody_miss(2, expected_subtest, expected_result)
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index b5c8942c..a8aaf1d2 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -723,7 +723,7 @@
             'crash_reporter::.*CrashKey.*',
 
             # Useful for platform-specific code.
-            'base::mac::(CFToNSPtrCast|NSToCFPtrCast|CFToNSOwnershipCast|NSToCFOwnershipCast)',
+            'base::apple::(CFToNSPtrCast|NSToCFPtrCast|CFToNSOwnershipCast|NSToCFOwnershipCast)',
             'base::mac::Is(AtMost|AtLeast)?OS.+',
             'base::ScopedCFTypeRef',
         ],
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_github.py b/third_party/blink/tools/blinkpy/w3c/wpt_github.py
index 1293edd..624f0e2d 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_github.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_github.py
@@ -292,7 +292,7 @@
                     WPT_GH_ORG, WPT_GH_REPO_NAME, EXPORT_PR_LABEL,
                     escaped_provisional_pr_label,
                     min(MAX_PER_PAGE, self._pr_history_window))
-        return self.fetch_pull_requests_from_path(path)
+        return self.fetch_pull_requests_from_path(path, min_expected_prs=200)
 
     @memoized
     def all_pull_requests(self):
@@ -310,7 +310,7 @@
                     min(MAX_PER_PAGE, self._pr_history_window))
         return self.fetch_pull_requests_from_path(path)
 
-    def fetch_pull_requests_from_path(self, path):
+    def fetch_pull_requests_from_path(self, path, min_expected_prs=1000):
         """Fetches PRs from url path.
 
         The maximum number of PRs is pr_history_window. Search endpoint is used
@@ -341,12 +341,12 @@
                                   'fetch all pull requests', path)
             path = self.extract_link_next(response.getheader('Link'))
 
-        # There are way more than 1000 exported PRs on GitHub, so we should
-        # always get at least pr_history_window PRs. This assertion is added to
-        # mitigate transient GitHub API issues (crbug.com/814617).
-        if len(all_prs) < self._pr_history_window:
-            raise GitHubError('at least %d commits' % self._pr_history_window,
-                              len(all_prs), 'fetch all pull requests')
+        # Doing this check to mitigate Github API issues (crbug.com/814617).
+        # Use a minimum based on which path it comes from
+        min_prs = min(self._pr_history_window, min_expected_prs)
+        if len(all_prs) < min_prs:
+            raise GitHubError('at least %d commits' % min_prs, len(all_prs),
+                              'fetch all pull requests')
 
         _log.info('Fetched %d PRs from GitHub.', len(all_prs))
         return all_prs
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/mac.py b/third_party/blink/tools/blinkpy/web_tests/port/mac.py
index 2f4c288..e8c66a47 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/mac.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/mac.py
@@ -132,3 +132,9 @@
                                             self.driver_name() + '.app',
                                             'Contents', 'MacOS',
                                             self.driver_name())
+
+    def _default_timeout_ms(self):
+        # increase timeout by 4x on older mac versions
+        if self._version in {'mac10.13', 'mac10.14'}:
+            return 4 * super()._default_timeout_ms()
+        return super()._default_timeout_ms()
\ No newline at end of file
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/mac_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/mac_unittest.py
index 6ab055a..8aef98c 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/mac_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/mac_unittest.py
@@ -105,3 +105,13 @@
         port = self.make_port(os_version='mac10.14')
         port.host.platform = all_tests_platform
         self.assertTrue(port.default_smoke_test_only())
+
+    def test_default_timeout_ms(self):
+        port = self.make_port(os_version='mac11')
+        default_timeout = port._default_timeout_ms()
+
+        port = self.make_port(os_version='mac10.13')
+        self.assertEquals(4 * default_timeout, port._default_timeout_ms())
+
+        port = self.make_port(os_version='mac10.14')
+        self.assertEquals(4 * default_timeout, port._default_timeout_ms())
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ffabebf..c329c2b 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -24,7 +24,9 @@
 #     skip tests for historical reasons.
 
 # Intentional failures to test the layout test system.
-harness-tests/crash.html [ Crash ]
+[ Linux ] harness-tests/crash.html [ Crash ]
+[ Mac ] harness-tests/crash.html [ Crash ]
+crbug.com/1420658 [ Win ] harness-tests/crash.html [ Crash Timeout ]
 harness-tests/timeout.html [ Timeout ]
 fast/harness/sample-fail-mismatch-reftest.html [ Failure ]
 
@@ -691,8 +693,8 @@
 crbug.com/1308431 external/wpt/css/css-masking/clip-path/clip-path-shape-002.html [ Failure ]
 crbug.com/1308431 external/wpt/css/css-masking/clip-path/clip-path-shape-003.html [ Failure ]
 crbug.com/1308431 external/wpt/css/css-masking/clip-path/clip-path-shape-004.html [ Failure ]
-crbug.com/1308431 external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-001.html [ Failure ]
-crbug.com/1308431 external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-002.html [ Failure ]
+crbug.com/1308431 external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-001.html [ Failure ]
+crbug.com/1308431 external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-002.html [ Failure ]
 
 # Fails, at a minimum, due to lack of support for CSS mask property in html elements
 crbug.com/432153 external/wpt/svg/painting/reftests/display-none-mask.html [ Crash Failure Pass Timeout ]
@@ -2920,6 +2922,7 @@
 crbug.com/626703 [ Win ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Mac13 ] external/wpt/html/semantics/links/hyperlink-auditing/headers.optional.html [ Timeout ]
 crbug.com/626703 [ Mac13 ] external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-video.html [ Failure ]
 crbug.com/626703 [ Mac13 ] external/wpt/css/css-content/quotes-020.html [ Failure ]
 crbug.com/626703 [ Mac13 ] external/wpt/css/css-overflow/scrollbar-gutter-002.html [ Failure ]
@@ -2936,12 +2939,9 @@
 crbug.com/626703 external/wpt/css/css-inline/text-box-trim/text-box-trim-half-leading-block-box-001-ref.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-inline/text-box-trim/text-box-trim-half-leading-block-box-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-inline/text-box-trim/text-box-trim-half-leading-inline-box-002.html [ Failure ]
-crbug.com/626703 external/wpt/preload/modulepreload-as.html [ Failure Timeout ]
 crbug.com/626703 [ Mac13 ] virtual/fenced-frame-mparch/external/wpt/fenced-frame/background-sync.https.html [ Timeout ]
 crbug.com/626703 [ Mac13 ] virtual/pending-beacon/external/wpt/pending-beacon/pending_beacon-sendonhidden.tentative.https.window.html [ Timeout ]
 crbug.com/626703 [ Mac13 ] virtual/prefetch/external/wpt/speculation-rules/prefetch/out-of-document-rule-set.https.html?include=RelativeUrlForCandidate [ Timeout ]
-crbug.com/626703 external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering-2.html [ Failure ]
-crbug.com/626703 external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-nesting/conditional-rules.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/popovers/popover-hide-crash.html [ Timeout ]
 crbug.com/626703 [ Mac13-arm64 ] external/wpt/css/css-overflow/scrollbar-gutter-002.html [ Failure ]
@@ -6813,4 +6813,7 @@
 
 # Sheriff 2023-05-11
 crbug.com/1444592 [ Linux ] external/wpt/js-self-profiling/function-anonymous-names.https.html [ Failure ]
-crbug.com/1444626 [ Mac ] external/wpt/html/semantics/popovers/popover-hover-hide.tentative.html [ Failure ]
\ No newline at end of file
+crbug.com/1444626 [ Mac ] external/wpt/html/semantics/popovers/popover-hover-hide.tentative.html [ Failure ]
+
+# Fails on win-10-rel (only) with no stack trace or other help
+crbug.com/1444368 [ Win ] fast/selectors/specificity-overflow.html [ Crash Pass ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 1af1386..b82f772 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -355,12 +355,35 @@
   {
     "prefix": "composite-clip-path-animation",
     "platforms": ["Linux", "Mac", "Win"],
-    "bases": ["external/wpt/css/css-masking/clip-path/animations"],
+    "bases": ["external/wpt/css/css-masking/clip-path/animations",
+              "animations/responsive/interpolation/clip-path-responsive.html",
+              "css3/masking/clip-path-animation.html",
+              "fast/css/clip-path-crash.html",
+              "http/tests/devtools/animation/animation-timeline.js",
+              "http/tests/devtools/animation/animation-web-anim-negative-start-time.js",
+              "paint/invalidation/svg/invalid-clip-path-crash.html",
+              "transitions/webkit-clip-path-equality.html"],
+    "exclusive_tests": "ALL",
     "args": ["--enable-blink-features=CompositeClipPathAnimation",
              "--enable-threaded-compositing"],
     "expires": "Jul 1, 2023"
   },
   {
+    "prefix": "main-thread-clip-path-animation",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": ["external/wpt/css/css-masking/clip-path/animations",
+              "animations/responsive/interpolation/clip-path-responsive.html",
+              "css3/masking/clip-path-animation.html",
+              "fast/css/clip-path-crash.html",
+              "http/tests/devtools/animation/animation-timeline.js",
+              "http/tests/devtools/animation/animation-web-anim-negative-start-time.js",
+              "paint/invalidation/svg/invalid-clip-path-crash.html",
+              "transitions/webkit-clip-path-equality.html"],
+    "exclusive_tests": "ALL",
+    "args": ["--disable-blink-features=CompositeClipPathAnimation"],
+    "expires": "never"
+  },
+  {
     "prefix": "fractional-scroll-offsets",
     "platforms": ["Linux", "Mac", "Win"],
     "bases": ["external/wpt/css/css-position/sticky/",
@@ -1226,6 +1249,16 @@
     "expires": "Jul 1, 2023"
   },
   {
+    "prefix": "force-renderer-accessibility-form-controls",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": ["external/wpt/accessibility/crashtests/bdo-table-cell.html",
+              "external/wpt/accessibility/crashtests/displaylocked-serialize.html",
+              "external/wpt/accessibility/crashtests/img-map-pseudo.html",
+              "external/wpt/accessibility/crashtests/map-inside-map.html"],
+    "args": ["--force-renderer-accessibility=form-controls"],
+    "expires": "never"
+  },
+  {
     "prefix": "no-alloc-direct-call",
     "platforms": ["Linux"],
     "bases": ["fast/canvas"],
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 d484ebd..ed56e70 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
@@ -4533,6 +4533,13 @@
        {}
       ]
      ],
+     "inserthtml-in-li-in-option.html": [
+      "c8aa9f780f45b071b827704c564003808061c9b0",
+      [
+       null,
+       {}
+      ]
+     ],
      "inserthtml-in-text-adopted-to-other-document.html": [
       "d8234f917a2ae32790eb71cc62d12de7ba99323a",
       [
@@ -105054,6 +105061,19 @@
         {}
        ]
       ],
+      "container-units-rule-cache.html": [
+       "cc93f7793ae6bd57a8ee30e5d6322a6ff43e93cb",
+       [
+        null,
+        [
+         [
+          "/css/css-contain/container-queries/container-units-rule-cache-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "counters-in-container-dynamic.html": [
        "d85ab6cb425ba792914b39901cd3f3ba3943b606",
        [
@@ -135962,6 +135982,19 @@
         ],
         {}
        ]
+      ],
+      "writing-directions-001.html": [
+       "24caaebc4f181ca1b3acc5c759ce51c4b84cd15e",
+       [
+        null,
+        [
+         [
+          "/css/css-grid/subgrid/writing-directions-001-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
       ]
      },
      "table-grid-item-dynamic-001.html": [
@@ -146515,6 +146548,71 @@
          {}
         ]
        ],
+       "clip-path-path-interpolation-001.html": [
+        "0c988e090e6b5775242208909b51e7dc83876664",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-path-interpolation-001-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
+       "clip-path-path-interpolation-002.html": [
+        "4c1c485f7f814d6849b3dc41b0a1d2f0295068e3",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-path-interpolation-002-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
+       "clip-path-path-interpolation-with-zoom.html": [
+        "50dc5e6f4e75cf8a1abfb5a972d94e6db23fa8c9",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-path-interpolation-with-zoom-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
+       "clip-path-shape-interpolation-001.html": [
+        "dae7f24d37cb39b34728664a2d854dfd53bc8c48",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-path-interpolation-001-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
+       "clip-path-shape-interpolation-002.html": [
+        "6af23c37d6b7bb4ed6780769be720d446f30fc2b",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-path-interpolation-002-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
        "clip-path-transition-custom-timing-function.html": [
         "47b0cd6e751fe8c2282ce8c44746fa01d0340b30",
         [
@@ -147705,45 +147803,6 @@
         {}
        ]
       ],
-      "clip-path-path-interpolation-001.html": [
-       "9b12621b9d6bbd2575ce50ddd121faf69d436423",
-       [
-        null,
-        [
-         [
-          "/css/css-masking/clip-path/reference/clip-path-path-interpolation-001-ref.html",
-          "=="
-         ]
-        ],
-        {}
-       ]
-      ],
-      "clip-path-path-interpolation-002.html": [
-       "4cf6fb4a07d36891dd4785df5a742c6611f9dd0a",
-       [
-        null,
-        [
-         [
-          "/css/css-masking/clip-path/reference/clip-path-path-interpolation-002-ref.html",
-          "=="
-         ]
-        ],
-        {}
-       ]
-      ],
-      "clip-path-path-interpolation-with-zoom.html": [
-       "4d54708da198d2c35bc5980857a8248b9159d3d4",
-       [
-        null,
-        [
-         [
-          "/css/css-masking/clip-path/reference/clip-path-path-interpolation-with-zoom-ref.html",
-          "=="
-         ]
-        ],
-        {}
-       ]
-      ],
       "clip-path-path-with-zoom.html": [
        "5879917f36e7efb74db4b1aaeb8a05bee96f39cd",
        [
@@ -148124,32 +148183,6 @@
         {}
        ]
       ],
-      "clip-path-shape-interpolation-001.html": [
-       "1d881e5bd58e070b76b87f04ff8aa61544c67e41",
-       [
-        null,
-        [
-         [
-          "/css/css-masking/clip-path/reference/clip-path-path-interpolation-001-ref.html",
-          "=="
-         ]
-        ],
-        {}
-       ]
-      ],
-      "clip-path-shape-interpolation-002.html": [
-       "9d8ab65b2d99b522342d1ce2a599d404474ea188",
-       [
-        null,
-        [
-         [
-          "/css/css-masking/clip-path/reference/clip-path-path-interpolation-002-ref.html",
-          "=="
-         ]
-        ],
-        {}
-       ]
-      ],
       "clip-path-strokeBox-1a.html": [
        "31fec7516ca95ed541643936dbd831e52b9be83c",
        [
@@ -156709,7 +156742,7 @@
       ]
      ],
      "implicit-nesting.html": [
-      "05b9e04fa84dc464bb2fe3173199fbf350bbe3c1",
+      "0a76dedc5bf2c3f309dcdd1e8a9b0a9f37161e96",
       [
        null,
        [
@@ -177066,6 +177099,19 @@
        {}
       ]
      ],
+     "rules-groups.html": [
+      "d07aa0b765892ec5fb153228271add2dfa21bc6f",
+      [
+       null,
+       [
+        [
+         "/css/css-tables/rules-groups-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "subpixel-collapsed-borders-001.html": [
       "a74e7a104cd4d3ab79d5af201bee1abcf0aafc36",
       [
@@ -245866,7 +245912,7 @@
       ]
      ],
      "offset-path-coord-box-001.html": [
-      "d90ddcfb261ad2a70f8377b438e9be3307e024e3",
+      "527459c6fddcc4f5d4409f311e2fb7175df35531",
       [
        null,
        [
@@ -245879,7 +245925,7 @@
       ]
      ],
      "offset-path-coord-box-002.html": [
-      "40499053dcc28d115e331e1a46c7abfbdfabc49a",
+      "0abe4169400e1f8504628b17bc11bf8cd8e62a1a",
       [
        null,
        [
@@ -245892,7 +245938,7 @@
       ]
      ],
      "offset-path-coord-box-003.html": [
-      "99972fa081e19d3dc746bcc10d91751a43d16f11",
+      "6605d8e9a0d21dee7270ea99291aff45ea17f2e2",
       [
        null,
        [
@@ -246113,7 +246159,7 @@
       ]
      ],
      "offset-path-shape-circle-001.html": [
-      "13df077df06f81bac68b537be14bbc04967bcecd",
+      "4874ba339298a18fb0a733e0254053579bbe50ac",
       [
        null,
        [
@@ -246142,7 +246188,7 @@
       ]
      ],
      "offset-path-shape-circle-002.html": [
-      "8264abbbca0901c5b477a7075c29ad968bcf27ef",
+      "672396186a57b8229457439c003aa9d6b1b62f4d",
       [
        null,
        [
@@ -246155,7 +246201,7 @@
       ]
      ],
      "offset-path-shape-circle-003.html": [
-      "eb00dacab30e1763ff0fed7405ee8a215001982d",
+      "1911d425a16997d6a6da3edb0474e527c8b1e2b6",
       [
        null,
        [
@@ -246184,7 +246230,7 @@
       ]
      ],
      "offset-path-shape-circle-004.html": [
-      "39e80fa861b10c0bfed22469f00d2c5a8f42daf2",
+      "f0a8523dd16f15075828d9bcbbf31f62a97ff35d",
       [
        null,
        [
@@ -246197,7 +246243,7 @@
       ]
      ],
      "offset-path-shape-ellipse-001.html": [
-      "35482636ba169d221f852c6c0dcb3ca6e80cf145",
+      "b8254458d38d71038f47dd80ffddbd971190b665",
       [
        null,
        [
@@ -246226,7 +246272,7 @@
       ]
      ],
      "offset-path-shape-ellipse-002.html": [
-      "298e1dd52edb5c09ff8d369a488d6937e3bce404",
+      "461838aa8899a8326dc18034fd7c530983749cfc",
       [
        null,
        [
@@ -246239,7 +246285,7 @@
       ]
      ],
      "offset-path-shape-ellipse-003.html": [
-      "2b79367b0bcb2773dd295ea6d0bd627d05d3997c",
+      "540c56dba7b39ccb93f29e766c9178585c82238a",
       [
        null,
        [
@@ -246252,7 +246298,7 @@
       ]
      ],
      "offset-path-shape-ellipse-004.html": [
-      "2c95415b11f266938c72363d6d4383121c7350fa",
+      "0ed4816e3d0706730004f98bb2c7c0cbc644865a",
       [
        null,
        [
@@ -264957,7 +265003,7 @@
    "selection": {
     "caret": {
      "collapse-pre-linestart-1.html": [
-      "6863456f06e0173789634eff5a8ec22035f91388",
+      "e8bd262868cb1bdc2734a50a934d3222e6d69fe0",
       [
        null,
        [
@@ -264970,7 +265016,7 @@
       ]
      ],
      "collapse-pre-linestart-2.html": [
-      "ac119cbb3167ee5e5f88742420d849b15dd58664",
+      "0feee464c98a66a73eb718dbaf76061a67a08610",
       [
        null,
        [
@@ -272058,11 +272104,11 @@
   "support": {
    ".cache": {
     "gitignore2.json": [
-     "cd7d1a90bad182741f05e8847d1e7e50537859f9",
+     "77cc0f9907dfbb638badda5f1d0a42e864a96985",
      []
     ],
     "mtime.json": [
-     "c82378988f5242054dcc8739a6c5c3f4c9160591",
+     "870fae931b1edc95a2627598516c6ca556f65df5",
      []
     ]
    },
@@ -283591,6 +283637,10 @@
        "fce3a35f2a4b8f3cc95a7722e72ee49fe074760b",
        []
       ],
+      "block-in-inline-append-001.xht.ini": [
+       "9f8014a4bc60c9eacd5bffe8e1102fdc1e547daf",
+       []
+      ],
       "block-in-inline-baseline-001-ref.html": [
        "35b68ceef65dbf91ecf51069a4f9f3ca007ec45c",
        []
@@ -287917,6 +287967,30 @@
        "c32e08624afa28bb4997479d40f539ed4a52b1ef",
        []
       ],
+      "animation-delay-end-computed.tentative-expected.txt": [
+       "6d84745e7b18113980ac769b7f38c327f18f91ec",
+       []
+      ],
+      "animation-delay-end-valid.tentative-expected.txt": [
+       "71233d1aebe285e7a8989c8ffda59728099e3bb8",
+       []
+      ],
+      "animation-delay-shorthand-computed-expected.txt": [
+       "94b49acfe099599de5d1d60c9d22eae698e344fd",
+       []
+      ],
+      "animation-delay-shorthand-expected.txt": [
+       "4663aaaf0e2dd075bb6f7ddba292c4f351d9ced1",
+       []
+      ],
+      "animation-delay-start-computed.tentative-expected.txt": [
+       "75dec90a103d4e638b8eaf8369f9d97c72d9c650",
+       []
+      ],
+      "animation-delay-start-valid.tentative-expected.txt": [
+       "3d4cc51069d8ddada0b28d2cd6f1459ccde7ad99",
+       []
+      ],
       "animation-name-computed-expected.txt": [
        "bbe7ac5483167f5926f25f928dd8e1df04956ac6",
        []
@@ -291843,8 +291917,12 @@
        "9f2446cc0e3668b8d01feaf7fe1f4db0c2ce2c0b",
        []
       ],
+      "color-valid-color-mix-function-expected.txt": [
+       "7749c322d17be1529402df805dc108da5e0d0c7d",
+       []
+      ],
       "color-valid-color-mix-function.html.ini": [
-       "a6532b8e209afedbab2c65756d1690a0a18e7b82",
+       "ed79ae167f43a189efc9c55b3147ade5d7259f78",
        []
       ],
       "color-valid-lab-expected.txt": [
@@ -292661,6 +292739,10 @@
        "dbdabd9bc4ecee3eec34406e7fd13a1e04141978",
        []
       ],
+      "container-units-rule-cache-ref.html": [
+       "6c8261959f674aa8a80f1f1f8fda343e64450114",
+       []
+      ],
       "counters-ref.html": [
        "303c1e89bdfe5449177a2d144f4be1e1760a80d5",
        []
@@ -307420,6 +307502,10 @@
       "subgrid-stretch-ref.html": [
        "33e8669da01787826ea27895323f17b5b3a6a2c9",
        []
+      ],
+      "writing-directions-001-ref.html": [
+       "90612516889e8feb5b5b0ce6820b44d8531ab8be",
+       []
       ]
      },
      "support": {
@@ -307957,22 +308043,6 @@
       []
      ],
      "image-set": {
-      "image-set-negative-resolution-rendering-2.html.ini": [
-       "722e601f6eb6b34297995ac1edecd7d7840b3d9c",
-       []
-      ],
-      "image-set-negative-resolution-rendering.html.ini": [
-       "a5b715bce7095d572dda667ebf30fde5dcd6db15",
-       []
-      ],
-      "image-set-parsing-expected.txt": [
-       "db23524449650599895d0a7f4ba954ac6c6b61bd",
-       []
-      ],
-      "image-set-parsing.html.ini": [
-       "ac11ff01896eb1d7ecbdcecae6965822059570a6",
-       []
-      ],
       "image-set-resolution-001-ref.html": [
        "46c4d729ed59525b1a138f1f60aa6e6074251211",
        []
@@ -310704,6 +310774,26 @@
         "99126195a13b6e09a9e6080277a454d19c8576bf",
         []
        ],
+       "clip-path-path-interpolation-001-ref.html": [
+        "4e26ac61f62a61c3edb91d91acbd1aa1d5b35655",
+        []
+       ],
+       "clip-path-path-interpolation-002-ref.html": [
+        "567764a30124c2bc8821a508e2a7f8b6f0787c7e",
+        []
+       ],
+       "clip-path-path-interpolation-with-zoom-ref.html": [
+        "7e0d2a54266f3855dbc8e8e87167a06ea0cc323b",
+        []
+       ],
+       "clip-path-shape-interpolation-001.html.ini": [
+        "87f511da9191cd6a1755392f1ae10dc3f5a09211",
+        []
+       ],
+       "clip-path-shape-interpolation-002.html.ini": [
+        "7bdee6ab1a84131f1600cd22fd3b608d7eb0ea30",
+        []
+       ],
        "clip-path-transition-ref.html": [
         "af164c30f06808c3394cf3210ca13bc41792d53c",
         []
@@ -310985,14 +311075,6 @@
        "1fb9548298be40be84ae08438674516e1e915a02",
        []
       ],
-      "clip-path-shape-interpolation-001.html.ini": [
-       "87f511da9191cd6a1755392f1ae10dc3f5a09211",
-       []
-      ],
-      "clip-path-shape-interpolation-002.html.ini": [
-       "7bdee6ab1a84131f1600cd22fd3b608d7eb0ea30",
-       []
-      ],
       "clip-path-strokeBox-1a.html.ini": [
        "8193e11f12d30b391930905a53ff9dc1c933888b",
        []
@@ -311074,18 +311156,6 @@
         "d9ea5183fb1e6aa745c01f02e702df065d4945e6",
         []
        ],
-       "clip-path-path-interpolation-001-ref.html": [
-        "4e26ac61f62a61c3edb91d91acbd1aa1d5b35655",
-        []
-       ],
-       "clip-path-path-interpolation-002-ref.html": [
-        "567764a30124c2bc8821a508e2a7f8b6f0787c7e",
-        []
-       ],
-       "clip-path-path-interpolation-with-zoom-ref.html": [
-        "7e0d2a54266f3855dbc8e8e87167a06ea0cc323b",
-        []
-       ],
        "clip-path-path-with-zoom-ref.html": [
         "ef91c619c40615f633c64d59bfe302fcfbd8c5fb",
         []
@@ -313159,7 +313229,7 @@
       []
      ],
      "implicit-nesting-ref.html": [
-      "006216b60e71d2d1af9c3d6c9fc81ea4c53f1d1d",
+      "0057a67fd0370aec24e84df9f932d1da4e4f7f31",
       []
      ],
      "nest-containing-forgiving-ref.html": [
@@ -319456,6 +319526,10 @@
       "68cbcfc9c1ec6aa1fb940f5b26735e3b2fa3587e",
       []
      ],
+     "rules-groups-ref.html": [
+      "8da4884f80b690933ed9ea9fb17454474422fd44",
+      []
+     ],
      "subpixel-collapsed-borders-ref.html": [
       "0a2e7a8d95b95dc21ad1c103fdfba65b8f5b1b86",
       []
@@ -328094,8 +328168,12 @@
         "43f6e799905d8a6f84c7d452ff9a07e691760891",
         []
        ],
-       "animation-delay-expected.txt": [
-        "236b5bbbaf3f31d295da02e6e251830608b840aa",
+       "animation-delay-end.tentative-expected.txt": [
+        "cd252b859a8950fe843a7f0fd53d6e4de057096d",
+        []
+       ],
+       "animation-delay-start.tentative-expected.txt": [
+        "2ec7b3a8a1271025b75e20352a77705d4afcd04f",
         []
        ],
        "animation-delay.html.ini": [
@@ -329156,7 +329234,7 @@
        []
       ],
       "kind-of-widget-fallback-input-reset-border-image-repeat-001.html.ini": [
-       "57160a00e626a0503fd5203c7be46139a85c42b1",
+       "fc0838c06b72d4e544e40404313644fc594f5edc",
        []
       ],
       "kind-of-widget-fallback-input-reset-border-image-source-001.html.ini": [
@@ -329436,7 +329514,7 @@
        []
       ],
       "kind-of-widget-fallback-input-submit-border-block-start-color-001.html.ini": [
-       "476d6028b86525673cadb3186fe083df6d4b0a64",
+       "faaa5d20498c3e881ef28134e4b5840b53986172",
        []
       ],
       "kind-of-widget-fallback-input-submit-border-block-start-style-001.html.ini": [
@@ -329899,6 +329977,10 @@
        "7ab06ede5e4578ee34c04676699dd37957e38ff5",
        []
       ],
+      "kind-of-widget-fallback-textarea-border-inline-end-style-001.html.ini": [
+       "2555c731437098a65182a6af3c2b34dba04eb456",
+       []
+      ],
       "kind-of-widget-fallback-textarea-border-inline-start-width-001.html.ini": [
        "e8b5a37ee7faed92ea84fc866d8e9187e7841639",
        []
@@ -331386,8 +331468,12 @@
       "944e180f8d1ccbca7dc50952e92ff26bf580db6f",
       []
      ],
+     "calc-infinity-nan-serialize-resolution-expected.txt": [
+      "fa5daba651eec8e455adfaa4d413d85df9eb3740",
+      []
+     ],
      "calc-infinity-nan-serialize-resolution.html.ini": [
-      "26ac406e8466e7194850b11b29e39da864fdd939",
+      "9fba4d756475f251760ebe640de60f5f03f27ebd",
       []
      ],
      "calc-margin-block-1-ref.html": [
@@ -337587,15 +337673,15 @@
       []
      ],
      "offset-path-coord-box-001-ref.html": [
-      "5a9b5c0a1c2cc09e536b0017c3ce3a411099e944",
+      "679b43fe705e67ff512b63c29a3f47b578623dbc",
       []
      ],
      "offset-path-coord-box-002-ref.html": [
-      "4bd002577afd31c564f82b19be2f9682cfedf018",
+      "a722ec3bd3e2e009b24a03eb6dd528be0ff13483",
       []
      ],
      "offset-path-coord-box-003-ref.html": [
-      "b8a7ea3a42d1be520c65e7871f4a4e29b459ba5f",
+      "f6e6203f4a0caaa759c0dd4b2987121832d89a84",
       []
      ],
      "offset-path-geometry-box-ref.html": [
@@ -349639,14 +349725,6 @@
      "cc38797fcf4c0281d60a289a06c01a0d17c3b236",
      []
     ],
-    "FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker-expected.txt": [
-     "cfc8da4d2a1950cdc3e61fc1e525ee52786c995c",
-     []
-    ],
-    "FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js.ini": [
-     "5c367a2e6cc0a2f89edaacfd669fb3fcefd8abe0",
-     []
-    ],
     "META.yml": [
      "23d7765cdfa39689ef72ae6a0beb15b8b4292ecd",
      []
@@ -352114,10 +352192,6 @@
         "f08cf5de3e8d6220a5ace3027d789f8ac968d9e6",
         []
        ],
-       "no-browsing-context.window-expected.txt": [
-        "12dc8bca01799883ee0800e88e50b31ffb54fbea",
-        []
-       ],
        "no-browsing-context.window.js.ini": [
         "5f16db379e8dc9c5e9e41b7fa7218d2310513f90",
         []
@@ -353209,10 +353283,6 @@
        "5bcd71c98ebefc4d6bfa1a475e731f108ec7136e",
        []
       ],
-      "document-domain-removed-iframe-expected.txt": [
-       "5ae578d930f8a9e29d4f6396299d09efe167e728",
-       []
-      ],
       "document-domain-removed-iframe.html.ini": [
        "e5b382c20127f843d9758c0b0300ade3ca4ff709",
        []
@@ -369942,10 +370012,6 @@
         "3e715502b971d4f8282d1e05a8ccfad6f7037910",
         []
        ],
-       "url.window-expected.txt": [
-        "1e6f7125ab0d88e02cb8497ac6eefa5e4e9cbea2",
-        []
-       ],
        "url.window.js.ini": [
         "12ccbc57f5c36ec1e06f582aad556940bf349f6a",
         []
@@ -379223,14 +379289,6 @@
      "83670cd86e34c3cbf3b465428b8ff6848d51f534",
      []
     ],
-    "modulepreload-as.html.ini": [
-     "9e9e21f4ed99dd72ea7e42df396f5a72d37e49af",
-     []
-    ],
-    "modulepreload-expected.txt": [
-     "1cef0afff4d423bc95ea3e824f0c0fda7b3c9559",
-     []
-    ],
     "modulepreload.html.ini": [
      "fe059ec2a207db9a8e068259a49e867599cb4e91",
      []
@@ -382588,6 +382646,10 @@
      []
     ],
     "css": {
+     "animation-shorthand-expected.txt": [
+      "5b9ea08d71d542889f1653491829e3421a452735",
+      []
+     ],
      "animation-update-ref.html": [
       "7e375a1df7f063ba6cdbbdad92beb73915b84d6e",
       []
@@ -393133,7 +393195,11 @@
        "f67ac706866e6dcbcce770d3bdd637e4e0af59ce",
        []
       ]
-     }
+     },
+     "reuse-web-bundle-resource.https.tentative.html.ini": [
+      "c43d33dc5ec1c9daab4a5f77ee453eb89a638991",
+      []
+     ]
     }
    },
    "web-locks": {
@@ -401590,10 +401656,6 @@
      }
     },
     "modules": {
-     "dedicated-worker-import-meta-expected.txt": [
-      "229c339fe84726d41b1e1ee9b92d1a8428cef90a",
-      []
-     ],
      "dedicated-worker-import-meta.html.ini": [
       "1457ee30dc6f11cbb7c828c35e02aecbb3d8d32a",
       []
@@ -439449,21 +439511,21 @@
         {}
        ]
       ],
-      "animation-delay-end-computed.html": [
+      "animation-delay-end-computed.tentative.html": [
        "77f96706383df2fb9a25478b5b35807b67bedf2d",
        [
         null,
         {}
        ]
       ],
-      "animation-delay-end-invalid.html": [
+      "animation-delay-end-invalid.tentative.html": [
        "7cabd4e8e54d96d7c3b36fc9473022cf530f664d",
        [
         null,
         {}
        ]
       ],
-      "animation-delay-end-valid.html": [
+      "animation-delay-end-valid.tentative.html": [
        "162c781bb010066bfb17e87f64932180abb472c8",
        [
         null,
@@ -439491,21 +439553,21 @@
         {}
        ]
       ],
-      "animation-delay-start-computed.html": [
+      "animation-delay-start-computed.tentative.html": [
        "bfb89d0267f5ad9a5ef7cb01e8ad6dc4b536a2fc",
        [
         null,
         {}
        ]
       ],
-      "animation-delay-start-invalid.html": [
+      "animation-delay-start-invalid.tentative.html": [
        "bff31f3789a4fd9e8ff8be99e1af61882d46af62",
        [
         null,
         {}
        ]
       ],
-      "animation-delay-start-valid.html": [
+      "animation-delay-start-valid.tentative.html": [
        "f52286444edb71cdeb24bc57f8de8d566113730a",
        [
         null,
@@ -439708,13 +439770,6 @@
         {}
        ]
       ],
-      "animation-shorthand.tentative.html": [
-       "04d06080e5efd7b9a00bfc6d5e191902c0396cde",
-       [
-        null,
-        {}
-       ]
-      ],
       "animation-valid.html": [
        "0c5d5713e3e335faeb4db6ae75062a1f894b2d3d",
        [
@@ -442101,7 +442156,7 @@
        ]
       ],
       "color-valid-color-mix-function.html": [
-       "6e5a129fa0a883cebca2f45b1714dbb6314cfab4",
+       "659117a23b0ddd073d7f8a243e4f2dd86a7e4bff",
        [
         null,
         {}
@@ -442136,7 +442191,7 @@
        ]
       ],
       "color-valid-rgb.html": [
-       "14bcce854fed900b405f59a72c050d3d25527dbe",
+       "eb50db02cb2c18f3838921881e0b3aa290715112",
        [
         null,
         {}
@@ -452949,7 +453004,7 @@
       ]
      ],
      "parsing.html": [
-      "8e445faf1c9b8b0942f684d5093c5a7071d43a91",
+      "c22eaada3c27489fccdea579111d44e32e9839b4",
       [
        null,
        {}
@@ -456011,7 +456066,7 @@
       ]
      ],
      "scroll-margin-visibility-check.html": [
-      "a072c142ad54e552dd84a900599f2df9cdf28651",
+      "b41ccb36fd37b460b378f1f323241b84863b119f",
       [
        null,
        {}
@@ -456088,7 +456143,7 @@
       ]
      ],
      "scroll-target-margin-005.html": [
-      "5e4782fe653077c0f0e5ff144c3b60b588b691ae",
+      "d3b60270e602b07a1c2b9e14f48e840fe92b7c3b",
       [
        null,
        {}
@@ -456327,6 +456382,27 @@
         {}
        ]
       ],
+      "scroll-start-target-computed.html": [
+       "5dedf2483137e18bd5795d771aa2084949b3371a",
+       [
+        null,
+        {}
+       ]
+      ],
+      "scroll-start-target-invalid.html": [
+       "a08ef87237efbf383a4b6b8b9cef05c3e45e7305",
+       [
+        null,
+        {}
+       ]
+      ],
+      "scroll-start-target-valid.html": [
+       "f972967acd5c63f2d1e852711792da2a276bbe35",
+       [
+        null,
+        {}
+       ]
+      ],
       "scroll-start-valid.html": [
        "c472979543992628ba27bbbf8cceb1f06ade0a53",
        [
@@ -511226,7 +511302,7 @@
      ]
     ],
     "FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js": [
-     "1b7fcda35bf9aada9b7a1a60205525f7a4383349",
+     "dbecc24c1da5de78da9640fe1f7d0a5a5bfdc6a0",
      [
       "fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.html",
       {}
@@ -555015,7 +555091,7 @@
         ]
        ],
        "focus-after-close.html": [
-        "d66d45527a7e39fa96ee11d9a331bcc2109aeb37",
+        "93baf65cf60c17d9292525e110faff15bcbd52b9",
         [
          null,
          {
@@ -584372,22 +584448,8 @@
       {}
      ]
     ],
-    "modulepreload-as.html": [
-     "dd946e454a1fe1833dbe37164105f028795facba",
-     [
-      null,
-      {}
-     ]
-    ],
-    "modulepreload-sri.html": [
-     "ea32a6a302525240573deb75b6e3f16b96b5e1eb",
-     [
-      null,
-      {}
-     ]
-    ],
     "modulepreload.html": [
-     "bcd18c890fa836c500c86a7e5afb5c17a9f82f42",
+     "0e4b6923e32e83ac3b8f3018537352aa120df6c5",
      [
       null,
       {}
@@ -601000,7 +601062,7 @@
       ]
      ],
      "scroll-timeline-multi-pass.tentative.html": [
-      "91668ada5bed8528d1ee61ba15d5e52d20e1e5aa",
+      "651ba212ded9d0e64e8eb706f2af59c832d1fadc",
       [
        null,
        {}
@@ -605326,7 +605388,7 @@
       ]
      ],
      "detached-context.https.html": [
-      "747a953f6203758f0ff7d18fc6eb8cc9c19f17de",
+      "ce8e4cc8400c6a457c968a9d1250fde829369ed3",
       [
        null,
        {}
@@ -658735,7 +658797,7 @@
       ]
      ],
      "dedicated-worker-import-meta.html": [
-      "cff8e91488e8239abe213619d12d5a603072fbea",
+      "97a5da870f8d713287ace636660d759b9e61486b",
       [
        null,
        {}
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/block-in-inline-append-001.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/block-in-inline-append-001.xht.ini
new file mode 100644
index 0000000..9f8014a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/block-in-inline-append-001.xht.ini
@@ -0,0 +1,3 @@
+[block-in-inline-append-001.xht]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.tentative-expected.txt
new file mode 100644
index 0000000..6d84745e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL Property animation-delay-end value 'initial' assert_true: animation-delay-end doesn't seem to be supported in the computed style expected true got false
+FAIL Property animation-delay-end value '-500ms' assert_true: animation-delay-end doesn't seem to be supported in the computed style expected true got false
+FAIL Property animation-delay-end value 'calc(2 * 3s)' assert_true: animation-delay-end doesn't seem to be supported in the computed style expected true got false
+FAIL Property animation-delay-end value '20s, 10s' assert_true: animation-delay-end doesn't seem to be supported in the computed style expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.html
rename to third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-computed.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-invalid.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-invalid.html
rename to third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-invalid.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.tentative-expected.txt
new file mode 100644
index 0000000..71233d1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL e.style['animation-delay-end'] = "-5ms" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay-end'] = "0s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay-end'] = "10s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay-end'] = "20s, 10s" should set the property value assert_not_equals: property should be set got disallowed value ""
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.html
rename to third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-end-valid.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-shorthand-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-shorthand-computed-expected.txt
new file mode 100644
index 0000000..94b49acf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-shorthand-computed-expected.txt
@@ -0,0 +1,10 @@
+This is a testharness.js-based test.
+PASS Property animation-delay value '1s'
+PASS Property animation-delay value '-1s'
+FAIL Property animation-delay value '1s 2s' assert_true: '1s 2s' is a supported value for animation-delay. expected true got false
+PASS Property animation-delay value '1s, 2s'
+FAIL Property animation-delay value '1s 2s, 3s' assert_true: '1s 2s, 3s' is a supported value for animation-delay. expected true got false
+FAIL Property animation-delay value '1s, 2s 3s' assert_true: '1s, 2s 3s' is a supported value for animation-delay. expected true got false
+PASS Property animation-delay value '1s, 2s, 3s'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-shorthand-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-shorthand-expected.txt
new file mode 100644
index 0000000..4663aaaf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-shorthand-expected.txt
@@ -0,0 +1,34 @@
+This is a testharness.js-based test.
+PASS e.style['animation-delay'] = "1s" should set the property value
+PASS e.style['animation-delay'] = "-1s" should set the property value
+FAIL e.style['animation-delay'] = "1s 2s" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['animation-delay'] = "1s, 2s" should set the property value
+FAIL e.style['animation-delay'] = "1s 2s, 3s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay'] = "1s, 2s 3s" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['animation-delay'] = "1s, 2s, 3s" should set the property value
+PASS e.style['animation-delay'] = "1s 2s 3s" should not set the property value
+PASS e.style['animation-delay'] = "0s, 1s 2s 3s" should not set the property value
+PASS e.style['animation-delay'] = "1s / 2s" should not set the property value
+PASS e.style['animation-delay'] = "1s, 2px" should not set the property value
+PASS e.style['animation-delay'] = "#ff0000" should not set the property value
+PASS e.style['animation-delay'] = "red" should not set the property value
+PASS e.style['animation-delay'] = "thing" should not set the property value
+PASS e.style['animation-delay'] = "thing 0%" should not set the property value
+PASS e.style['animation-delay'] = "thing 42%" should not set the property value
+PASS e.style['animation-delay'] = "thing 100%" should not set the property value
+PASS e.style['animation-delay'] = "thing 100px" should not set the property value
+PASS e.style['animation-delay'] = "100% thing" should not set the property value
+FAIL e.style['animation-delay'] = "1s 2s" should set animation-delay-end assert_equals: animation-delay-end should be canonical expected (string) "2s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s 2s" should set animation-delay-start assert_equals: animation-delay-start should be canonical expected (string) "1s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s 2s" should not set unrelated longhands assert_true: expected true got false
+FAIL e.style['animation-delay'] = "1s" should set animation-delay-end assert_equals: animation-delay-end should be canonical expected (string) "0s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s" should set animation-delay-start assert_equals: animation-delay-start should be canonical expected (string) "1s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s" should not set unrelated longhands assert_equals: expected 0 but got 1
+FAIL e.style['animation-delay'] = "1s 2s, 3s 4s" should set animation-delay-end assert_equals: animation-delay-end should be canonical expected (string) "2s, 4s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s 2s, 3s 4s" should set animation-delay-start assert_equals: animation-delay-start should be canonical expected (string) "1s, 3s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s 2s, 3s 4s" should not set unrelated longhands assert_true: expected true got false
+FAIL e.style['animation-delay'] = "1s 2s, 3s, 4s 5s" should set animation-delay-end assert_equals: animation-delay-end should be canonical expected (string) "2s, 0s, 5s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s 2s, 3s, 4s 5s" should set animation-delay-start assert_equals: animation-delay-start should be canonical expected (string) "1s, 3s, 4s" but got (undefined) undefined
+FAIL e.style['animation-delay'] = "1s 2s, 3s, 4s 5s" should not set unrelated longhands assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.tentative-expected.txt
new file mode 100644
index 0000000..75dec90a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL Property animation-delay-start value 'initial' assert_true: animation-delay-start doesn't seem to be supported in the computed style expected true got false
+FAIL Property animation-delay-start value '-500ms' assert_true: animation-delay-start doesn't seem to be supported in the computed style expected true got false
+FAIL Property animation-delay-start value 'calc(2 * 3s)' assert_true: animation-delay-start doesn't seem to be supported in the computed style expected true got false
+FAIL Property animation-delay-start value '20s, 10s' assert_true: animation-delay-start doesn't seem to be supported in the computed style expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.html
rename to third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-computed.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-invalid.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-invalid.html
rename to third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-invalid.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.tentative-expected.txt
new file mode 100644
index 0000000..3d4cc51
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL e.style['animation-delay-start'] = "-5ms" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay-start'] = "0s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay-start'] = "10s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['animation-delay-start'] = "20s, 10s" should set the property value assert_not_equals: property should be set got disallowed value ""
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.html
rename to third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-delay-start-valid.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-shorthand.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-shorthand.tentative.html
deleted file mode 100644
index 04d0608..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-shorthand.tentative.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation">
-<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/css/support/shorthand-testcommon.js"></script>
-<script>
-// TODO(https://github.com/w3c/csswg-drafts/issues/8054): When support for
-// animation-delay-start and -end is added to the animation shorthand, this
-// file should just merge with animation-shorthand.html.
-test_shorthand_value('animation', 'anim paused both reverse 4 1s -3s cubic-bezier(0, -2, 1, 3)', {
-  'animation-duration': '1s',
-  'animation-timing-function': 'cubic-bezier(0, -2, 1, 3)',
-  'animation-delay-start': '-3s',
-  'animation-delay-end': '0s',
-  'animation-iteration-count': '4',
-  'animation-direction': 'reverse',
-  'animation-fill-mode': 'both',
-  'animation-play-state': 'paused',
-  'animation-name': 'anim',
-  'animation-timeline': 'auto',
-  'animation-range-start': 'normal',
-  'animation-range-end': 'normal',
-});
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function-expected.txt
new file mode 100644
index 0000000..7749c322
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function-expected.txt
@@ -0,0 +1,461 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = 3 duplicate test names: "e.style['color'] = "color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value", "e.style['color'] = "color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value", "e.style['color'] = "color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value"
+Found 456 tests; 440 PASS, 16 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS e.style['color'] = "color-mix(in srgb, red, blue)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, red, blue)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, red calc(20%), blue)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, red calc(var(--v)*1%), blue)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, currentcolor, blue)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, red 60%, blue 40%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, red, hsl(120, 100%, 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, 25% hsl(120deg 10% 20%), hsl(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%), 25% hsl(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%) 30%, hsl(30deg 30% 40%) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%) 12.5%, hsl(30deg 30% 40%) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, 25% hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4), 25% hsl(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4) 25%, hsl(30deg 30% 40% / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4) 30%, hsl(30deg 30% 40% / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4) 12.5%, hsl(30deg 30% 40% / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 10% 20% / .4) 0%, hsl(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl shorter hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl shorter hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl shorter hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl shorter hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl shorter hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl shorter hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl longer hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl longer hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl longer hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl longer hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl longer hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl longer hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl increasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl increasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl increasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl increasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl increasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl increasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl decreasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl decreasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl decreasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl decreasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl decreasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl decreasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value
+FAIL e.style['color'] = "color-mix(in hsl specified hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hsl specified hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hsl specified hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hsl specified hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hsl specified hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hsl specified hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['color'] = "color-mix(in hsl, hsl(none none none), hsl(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(none none none), hsl(30deg 40% 80%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 20% 40%), hsl(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 20% none), hsl(30deg 40% 60%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 20% 40%), hsl(30deg 20% none))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(none 20% 40%), hsl(30deg none 80%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40% / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40% / none))" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, lab(100 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)" should set the property value
+FAIL e.style['color'] = "color-mix(in hsl, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value assert_equals: serialization should be canonical expected "color-mix(in hsl, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" but got "color-mix(in hsl, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)"
+PASS e.style['color'] = "color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value
+FAIL e.style['color'] = "color-mix(in hsl, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value assert_equals: serialization should be canonical expected "color-mix(in hsl, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" but got "color-mix(in hsl, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)"
+PASS e.style['color'] = "color-mix(in hsl, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hsl, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, 25% hwb(120deg 10% 20%), hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%) 30%, hwb(30deg 30% 40%) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%) 12.5%, hwb(30deg 30% 40%) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, 25% hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4) 30%, hwb(30deg 30% 40% / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4) 12.5%, hwb(30deg 30% 40% / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / .4) 0%, hwb(30deg 30% 40% / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb shorter hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb shorter hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb shorter hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb shorter hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb shorter hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb shorter hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb longer hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb longer hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb longer hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb longer hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb longer hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb longer hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb increasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb increasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb increasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb increasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb increasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb increasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb decreasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb decreasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb decreasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb decreasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb decreasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb decreasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value
+FAIL e.style['color'] = "color-mix(in hwb specified hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hwb specified hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hwb specified hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hwb specified hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hwb specified hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['color'] = "color-mix(in hwb specified hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['color'] = "color-mix(in hwb, hwb(none none none), hwb(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(none none none), hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%), hwb(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% none), hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% none))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(none 10% 20%), hwb(30deg none 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40%))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40% / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40% / none))" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, lab(100 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)" should set the property value
+FAIL e.style['color'] = "color-mix(in hwb, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value assert_equals: serialization should be canonical expected "color-mix(in hwb, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" but got "color-mix(in hwb, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)"
+PASS e.style['color'] = "color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value
+FAIL e.style['color'] = "color-mix(in hwb, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value assert_equals: serialization should be canonical expected "color-mix(in hwb, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" but got "color-mix(in hwb, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)"
+PASS e.style['color'] = "color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg), lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg) 25%, lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, 25% lch(10 20 30deg), lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg), 25% lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg), lch(50 60 70deg) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg) 25%, lch(50 60 70deg) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg) 30%, lch(50 60 70deg) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg) 12.5%, lch(50 60 70deg) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg) 0%, lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4), lch(50 60 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4) 25%, lch(50 60 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, 25% lch(10 20 30deg / .4), lch(50 60 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4), 25% lch(50 60 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4), lch(50 60 70deg / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4) 25%, lch(50 60 70deg / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4) 30%, lch(50 60 70deg / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4) 12.5%, lch(50 60 70deg / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / .4) 0%, lch(50 60 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(100 0 40deg), lch(100 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(100 0 60deg), lch(100 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(100 0 50deg), lch(100 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(100 0 330deg), lch(100 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(100 0 20deg), lch(100 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(100 0 320deg), lch(100 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch shorter hue, lch(100 0 40deg), lch(100 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch shorter hue, lch(100 0 60deg), lch(100 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch shorter hue, lch(100 0 50deg), lch(100 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch shorter hue, lch(100 0 330deg), lch(100 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch shorter hue, lch(100 0 20deg), lch(100 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch shorter hue, lch(100 0 320deg), lch(100 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch longer hue, lch(100 0 40deg), lch(100 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch longer hue, lch(100 0 60deg), lch(100 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch longer hue, lch(100 0 50deg), lch(100 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch longer hue, lch(100 0 330deg), lch(100 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch longer hue, lch(100 0 20deg), lch(100 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch longer hue, lch(100 0 320deg), lch(100 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch increasing hue, lch(100 0 40deg), lch(100 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch increasing hue, lch(100 0 60deg), lch(100 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch increasing hue, lch(100 0 50deg), lch(100 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch increasing hue, lch(100 0 330deg), lch(100 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch increasing hue, lch(100 0 20deg), lch(100 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch increasing hue, lch(100 0 320deg), lch(100 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, lch(100 0 40deg), lch(100 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, lch(100 0 60deg), lch(100 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, lch(100 0 50deg), lch(100 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, lch(100 0 330deg), lch(100 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, lch(100 0 20deg), lch(100 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch decreasing hue, lch(100 0 320deg), lch(100 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(none none none), lch(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(none none none), lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg), lch(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 none), lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg), lch(50 60 none))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(none 20 30deg), lch(50 none 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / none), lch(50 60 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / none), lch(50 60 70deg / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in lch, lch(10 20 30deg / none), lch(50 60 70deg / none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg), oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg) 25%, oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, 25% oklch(0.1 0.2 30deg), oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg), 25% oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg), oklch(0.5 0.6 70deg) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg) 25%, oklch(0.5 0.6 70deg) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg) 30%, oklch(0.5 0.6 70deg) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg) 12.5%, oklch(0.5 0.6 70deg) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg) 0%, oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4), oklch(0.5 0.6 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4) 25%, oklch(0.5 0.6 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, 25% oklch(0.1 0.2 30deg / .4), oklch(0.5 0.6 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4), 25% oklch(0.5 0.6 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4), oklch(0.5 0.6 70deg / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4) 25%, oklch(0.5 0.6 70deg / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4) 30%, oklch(0.5 0.6 70deg / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4) 12.5%, oklch(0.5 0.6 70deg / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / .4) 0%, oklch(0.5 0.6 70deg / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(1 0 40deg), oklch(1 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(1 0 60deg), oklch(1 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(1 0 50deg), oklch(1 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(1 0 330deg), oklch(1 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(1 0 20deg), oklch(1 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(1 0 320deg), oklch(1 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch shorter hue, oklch(1 0 40deg), oklch(1 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch shorter hue, oklch(1 0 60deg), oklch(1 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch shorter hue, oklch(1 0 50deg), oklch(1 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch shorter hue, oklch(1 0 330deg), oklch(1 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch shorter hue, oklch(1 0 20deg), oklch(1 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch shorter hue, oklch(1 0 320deg), oklch(1 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch longer hue, oklch(1 0 40deg), oklch(1 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch longer hue, oklch(1 0 60deg), oklch(1 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch longer hue, oklch(1 0 50deg), oklch(1 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch longer hue, oklch(1 0 330deg), oklch(1 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch longer hue, oklch(1 0 20deg), oklch(1 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch longer hue, oklch(1 0 320deg), oklch(1 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch increasing hue, oklch(1 0 40deg), oklch(1 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch increasing hue, oklch(1 0 60deg), oklch(1 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch increasing hue, oklch(1 0 50deg), oklch(1 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch increasing hue, oklch(1 0 330deg), oklch(1 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch increasing hue, oklch(1 0 20deg), oklch(1 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch increasing hue, oklch(1 0 320deg), oklch(1 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch decreasing hue, oklch(1 0 40deg), oklch(1 0 60deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch decreasing hue, oklch(1 0 60deg), oklch(1 0 40deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch decreasing hue, oklch(1 0 50deg), oklch(1 0 330deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch decreasing hue, oklch(1 0 330deg), oklch(1 0 50deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch decreasing hue, oklch(1 0 20deg), oklch(1 0 320deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch decreasing hue, oklch(1 0 320deg), oklch(1 0 20deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(none none none), oklch(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(none none none), oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg), oklch(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 none), oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg), oklch(0.5 0.6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(none 0.2 30deg), oklch(0.5 none 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / none), oklch(0.5 0.6 70deg))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / none), oklch(0.5 0.6 70deg / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in oklch, oklch(0.1 0.2 30deg / none), oklch(0.5 0.6 70deg / none))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30), lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30) 25%, lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, 25% lab(10 20 30), lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30), 25% lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30), lab(50 60 70) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30) 25%, lab(50 60 70) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30) 30%, lab(50 60 70) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30) 12.5%, lab(50 60 70) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30) 0%, lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4), lab(50 60 70 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4) 25%, lab(50 60 70 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, 25% lab(10 20 30 / .4), lab(50 60 70 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4), 25% lab(50 60 70 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4), lab(50 60 70 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4) 25%, lab(50 60 70 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4) 30%, lab(50 60 70 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4) 12.5%, lab(50 60 70 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / .4) 0%, lab(50 60 70 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(none none none), lab(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(none none none), lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30), lab(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 none), lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30), lab(50 60 none))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(none 20 30), lab(50 none 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / none), lab(50 60 70))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / none), lab(50 60 70 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in lab, lab(10 20 30 / none), lab(50 60 70 / none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3) 25%, oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, 25% oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3), 25% oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3) 25%, oklab(0.5 0.6 0.7) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3) 30%, oklab(0.5 0.6 0.7) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3) 12.5%, oklab(0.5 0.6 0.7) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3) 0%, oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4), oklab(0.5 0.6 0.7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4) 25%, oklab(0.5 0.6 0.7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, 25% oklab(0.1 0.2 0.3 / .4), oklab(0.5 0.6 0.7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4), 25% oklab(0.5 0.6 0.7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4), oklab(0.5 0.6 0.7 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4) 25%, oklab(0.5 0.6 0.7 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4) 30%, oklab(0.5 0.6 0.7 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4) 12.5%, oklab(0.5 0.6 0.7 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / .4) 0%, oklab(0.5 0.6 0.7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(none none none), oklab(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(none none none), oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 none), oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(none 0.2 0.3), oklab(0.5 none 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / none), oklab(0.5 0.6 0.7))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / none), oklab(0.5 0.6 0.7 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in oklab, oklab(0.1 0.2 0.3 / none), oklab(0.5 0.6 0.7 / none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3), color(srgb .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3) 25%, color(srgb .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3), color(srgb .5 .6 .7) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3) 25%, color(srgb .5 .6 .7) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3) 30%, color(srgb .5 .6 .7) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3) 12.5%, color(srgb .5 .6 .7) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3) 0%, color(srgb .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .5), color(srgb .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .4) 25%, color(srgb .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .4) 25%, color(srgb .5 .6 .7 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .4) 30%, color(srgb .5 .6 .7 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .4) 12.5%, color(srgb .5 .6 .7 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / .4) 0%, color(srgb .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb 2 3 4 / 5), color(srgb 4 6 8 / 10))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb -2 -3 -4), color(srgb -4 -6 -8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb -2 -3 -4 / -5), color(srgb -4 -6 -8 / -10))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb none none none), color(srgb none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb none none none), color(srgb .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3), color(srgb none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 none), color(srgb .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3), color(srgb .5 .6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb none .2 .3), color(srgb .5 none .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / none), color(srgb .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / none), color(srgb .5 .6 .7 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb, color(srgb .1 .2 .3 / none), color(srgb .5 .6 .7 / none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3), color(srgb-linear .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3) 25%, color(srgb-linear .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3), color(srgb-linear .5 .6 .7) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3) 25%, color(srgb-linear .5 .6 .7) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3) 30%, color(srgb-linear .5 .6 .7) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3) 12.5%, color(srgb-linear .5 .6 .7) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3) 0%, color(srgb-linear .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .5), color(srgb-linear .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .4) 25%, color(srgb-linear .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .4), color(srgb-linear .5 .6 .7 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .4) 25%, color(srgb-linear .5 .6 .7 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .4) 30%, color(srgb-linear .5 .6 .7 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .4) 12.5%, color(srgb-linear .5 .6 .7 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / .4) 0%, color(srgb-linear .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear 2 3 4 / 5), color(srgb-linear 4 6 8 / 10))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear -2 -3 -4), color(srgb-linear -4 -6 -8))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear -2 -3 -4 / -5), color(srgb-linear -4 -6 -8 / -10))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear none none none), color(srgb-linear none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear none none none), color(srgb-linear .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3), color(srgb-linear none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 none), color(srgb-linear .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3), color(srgb-linear .5 .6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear none .2 .3), color(srgb-linear .5 none .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / none), color(srgb-linear .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / none), color(srgb-linear .5 .6 .7 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in srgb-linear, color(srgb-linear .1 .2 .3 / none), color(srgb-linear .5 .6 .7 / none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3), color(xyz .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3) 25%, color(xyz .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3), color(xyz .5 .6 .7) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3) 25%, color(xyz .5 .6 .7) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3) 30%, color(xyz .5 .6 .7) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3) 12.5%, color(xyz .5 .6 .7) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3) 0%, color(xyz .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .5), color(xyz .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .4) 25%, color(xyz .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .4) 25%, color(xyz .5 .6 .7 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .4) 30%, color(xyz .5 .6 .7 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .4) 12.5%, color(xyz .5 .6 .7 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / .4) 0%, color(xyz .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz 2 3 4 / 5), color(xyz 4 6 8 / 10))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz -2 -3 -4), color(xyz -4 -6 -8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz -2 -3 -4 / -5), color(xyz -4 -6 -8 / -10))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz none none none), color(xyz none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz none none none), color(xyz .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3), color(xyz none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 none), color(xyz .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3), color(xyz .5 .6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz none .2 .3), color(xyz .5 none .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / none), color(xyz .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / none), color(xyz .5 .6 .7 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz, color(xyz .1 .2 .3 / none), color(xyz .5 .6 .7 / none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3), color(xyz-d50 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3) 25%, color(xyz-d50 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3), color(xyz-d50 .5 .6 .7) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3) 25%, color(xyz-d50 .5 .6 .7) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3) 30%, color(xyz-d50 .5 .6 .7) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3) 12.5%, color(xyz-d50 .5 .6 .7) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3) 0%, color(xyz-d50 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .5), color(xyz-d50 .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .4) 25%, color(xyz-d50 .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .4), color(xyz-d50 .5 .6 .7 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .4) 25%, color(xyz-d50 .5 .6 .7 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .4) 30%, color(xyz-d50 .5 .6 .7 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .4) 12.5%, color(xyz-d50 .5 .6 .7 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / .4) 0%, color(xyz-d50 .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 2 3 4 / 5), color(xyz-d50 4 6 8 / 10))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 -2 -3 -4), color(xyz-d50 -4 -6 -8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 -2 -3 -4 / -5), color(xyz-d50 -4 -6 -8 / -10))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 none none none), color(xyz-d50 none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 none none none), color(xyz-d50 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3), color(xyz-d50 none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 none), color(xyz-d50 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3), color(xyz-d50 .5 .6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 none .2 .3), color(xyz-d50 .5 none .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / none), color(xyz-d50 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / none), color(xyz-d50 .5 .6 .7 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d50, color(xyz-d50 .1 .2 .3 / none), color(xyz-d50 .5 .6 .7 / none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3), color(xyz-d65 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3) 25%, color(xyz-d65 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3), color(xyz-d65 .5 .6 .7) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3) 25%, color(xyz-d65 .5 .6 .7) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3) 30%, color(xyz-d65 .5 .6 .7) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3) 12.5%, color(xyz-d65 .5 .6 .7) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3) 0%, color(xyz-d65 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .5), color(xyz-d65 .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .4) 25%, color(xyz-d65 .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .4), color(xyz-d65 .5 .6 .7 / .8) 25%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .4) 25%, color(xyz-d65 .5 .6 .7 / .8) 75%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .4) 30%, color(xyz-d65 .5 .6 .7 / .8) 90%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .4) 12.5%, color(xyz-d65 .5 .6 .7 / .8) 37.5%)" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / .4) 0%, color(xyz-d65 .5 .6 .7 / .8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 2 3 4 / 5), color(xyz-d65 4 6 8 / 10))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 -2 -3 -4), color(xyz-d65 -4 -6 -8))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 -2 -3 -4 / -5), color(xyz-d65 -4 -6 -8 / -10))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 none none none), color(xyz-d65 none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 none none none), color(xyz-d65 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3), color(xyz-d65 none none none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 none), color(xyz-d65 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3), color(xyz-d65 .5 .6 none))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 none .2 .3), color(xyz-d65 .5 none .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / none), color(xyz-d65 .5 .6 .7))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / none), color(xyz-d65 .5 .6 .7 / 0.5))" should set the property value
+PASS e.style['color'] = "color-mix(in xyz-d65, color(xyz-d65 .1 .2 .3 / none), color(xyz-d65 .5 .6 .7 / none))" should set the property value
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html
index 6e5a129..659117a23 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html
@@ -79,6 +79,14 @@
     test_valid_value(`color`, `color-mix(in hsl decreasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))`, `color-mix(in hsl decreasing hue, rgb(191, 106, 64), rgb(191, 64, 149))`);
     test_valid_value(`color`, `color-mix(in hsl decreasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))`, `color-mix(in hsl decreasing hue, rgb(191, 64, 149), rgb(191, 106, 64))`);
 
+
+    test_valid_value(`color`, `color-mix(in hsl specified hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))`, `color-mix(in hsl specified hue, rgb(191, 149, 64), rgb(191, 191, 64))`);
+    test_valid_value(`color`, `color-mix(in hsl specified hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))`, `color-mix(in hsl specified hue, rgb(191, 191, 64), rgb(191, 149, 64))`);
+    test_valid_value(`color`, `color-mix(in hsl specified hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))`, `color-mix(in hsl specified hue, rgb(191, 170, 64), rgb(191, 64, 128))`);
+    test_valid_value(`color`, `color-mix(in hsl specified hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))`, `color-mix(in hsl specified hue, rgb(191, 64, 128), rgb(191, 170, 64))`);
+    test_valid_value(`color`, `color-mix(in hsl specified hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))`, `color-mix(in hsl specified hue, rgb(191, 106, 64), rgb(191, 64, 149))`);
+    test_valid_value(`color`, `color-mix(in hsl specified hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))`, `color-mix(in hsl specified hue, rgb(191, 64, 149), rgb(191, 106, 64))`);
+
     test_valid_value(`color`, `color-mix(in hsl, hsl(none none none), hsl(none none none))`, `color-mix(in hsl, rgb(0, 0, 0), rgb(0, 0, 0))`);
     test_valid_value(`color`, `color-mix(in hsl, hsl(none none none), hsl(30deg 40% 80%))`, `color-mix(in hsl, rgb(0, 0, 0), rgb(224, 204, 184))`);
     test_valid_value(`color`, `color-mix(in hsl, hsl(120deg 20% 40%), hsl(none none none))`, `color-mix(in hsl, rgb(82, 122, 82), rgb(0, 0, 0))`);
@@ -95,6 +103,9 @@
     test_valid_value(`color`, `color-mix(in hsl, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hsl, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hsl, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hsl, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hsl, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hsl, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hsl, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hsl, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`);
@@ -155,6 +166,13 @@
     test_valid_value(`color`, `color-mix(in hwb decreasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))`, `color-mix(in hwb decreasing hue, rgb(153, 102, 77), rgb(153, 77, 128))`);
     test_valid_value(`color`, `color-mix(in hwb decreasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))`, `color-mix(in hwb decreasing hue, rgb(153, 77, 128), rgb(153, 102, 77))`);
 
+    test_valid_value(`color`, `color-mix(in hwb specified hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))`, `color-mix(in hwb specified hue, rgb(153, 128, 77), rgb(153, 153, 77))`);
+    test_valid_value(`color`, `color-mix(in hwb specified hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))`, `color-mix(in hwb specified hue, rgb(153, 153, 77), rgb(153, 128, 77))`);
+    test_valid_value(`color`, `color-mix(in hwb specified hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))`, `color-mix(in hwb specified hue, rgb(153, 140, 77), rgb(153, 77, 115))`);
+    test_valid_value(`color`, `color-mix(in hwb specified hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))`, `color-mix(in hwb specified hue, rgb(153, 77, 115), rgb(153, 140, 77))`);
+    test_valid_value(`color`, `color-mix(in hwb specified hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))`, `color-mix(in hwb specified hue, rgb(153, 102, 77), rgb(153, 77, 128))`);
+    test_valid_value(`color`, `color-mix(in hwb specified hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))`, `color-mix(in hwb specified hue, rgb(153, 77, 128), rgb(153, 102, 77))`);
+
     test_valid_value(`color`, `color-mix(in hwb, hwb(none none none), hwb(none none none))`, `color-mix(in hwb, rgb(255, 0, 0), rgb(255, 0, 0))`);
     test_valid_value(`color`, `color-mix(in hwb, hwb(none none none), hwb(30deg 30% 40%))`, `color-mix(in hwb, rgb(255, 0, 0), rgb(153, 115, 77))`);
     test_valid_value(`color`, `color-mix(in hwb, hwb(120deg 10% 20%), hwb(none none none))`, `color-mix(in hwb, rgb(26, 204, 26), rgb(255, 0, 0))`);
@@ -170,6 +188,11 @@
     test_valid_value(`color`, `color-mix(in hwb, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hwb, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hwb, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hwb, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hwb, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`);
+    test_valid_value(`color`, `color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`);
+
     test_valid_value(`color`, `color-mix(in hwb, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`);
     test_valid_value(`color`, `color-mix(in hwb, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color-mix(in hwb, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html.ini b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html.ini
index a6532b8e..ed79ae16 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-color-mix-function.html.ini
@@ -1,30 +1,49 @@
 [color-valid-color-mix-function.html]
-  [e.style['color'\] = "color-mix(in hwb decreasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value]
+  expected: ERROR
+  [e.style['color'\] = "color-mix(in hsl specified hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb decreasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl specified hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb increasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl specified hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb increasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl specified hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb longer hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl specified hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb longer hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl specified hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb shorter hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb shorter hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hsl, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hwb specified hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%))" should set the property value]
     expected: FAIL
 
-  [e.style['color'\] = "color-mix(in hwb, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value]
+  [e.style['color'\] = "color-mix(in hwb specified hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%))" should set the property value]
+    expected: FAIL
+
+  [e.style['color'\] = "color-mix(in hwb specified hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%))" should set the property value]
+    expected: FAIL
+
+  [e.style['color'\] = "color-mix(in hwb specified hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%))" should set the property value]
+    expected: FAIL
+
+  [e.style['color'\] = "color-mix(in hwb specified hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%))" should set the property value]
+    expected: FAIL
+
+  [e.style['color'\] = "color-mix(in hwb specified hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%))" should set the property value]
+    expected: FAIL
+
+  [e.style['color'\] = "color-mix(in hwb, oklab(100 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)" should set the property value]
+    expected: FAIL
+
+  [e.style['color'\] = "color-mix(in hwb, oklch(100 0.399 336.3) 100%, rgb(0, 0, 0) 0%)" should set the property value]
     expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-rgb.html b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-rgb.html
index 14bcce8..eb50db0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-rgb.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/color-valid-rgb.html
@@ -28,6 +28,18 @@
 test_valid_value("color", "rgba(20% none none)", "rgb(51, 0, 0)");
 test_valid_value("color", "rgba(20% none none / none)", "rgba(51, 0, 0, 0)");
 test_valid_value("color", "rgba(none none none / 50%)", "rgba(0, 0, 0, 0.5)");
+test_valid_value("color", "rgb(-2 3 4)", "rgb(0, 3, 4)");
+test_valid_value("color", "rgb(-20% 20% 40%)", "rgb(0, 51, 102)");
+test_valid_value("color", "rgb(257 30 40)", "rgb(255, 30, 40)");
+test_valid_value("color", "rgb(250% 20% 40%)", "rgb(255, 51, 102)");
+test_valid_value("color", "rgba(-2 3 4)", "rgb(0, 3, 4)");
+test_valid_value("color", "rgba(-20% 20% 40%)", "rgb(0, 51, 102)");
+test_valid_value("color", "rgba(257 30 40)", "rgb(255, 30, 40)");
+test_valid_value("color", "rgba(250% 20% 40%)", "rgb(255, 51, 102)");
+test_valid_value("color", "rgba(-2 3 4 / .5)", "rgba(0, 3, 4, 0.5)");
+test_valid_value("color", "rgba(-20% 20% 40% / 50%)", "rgba(0, 51, 102, 0.5)");
+test_valid_value("color", "rgba(257 30 40 / 50%)", "rgba(255, 30, 40, 0.5)");
+test_valid_value("color", "rgba(250% 20% 40% / .5)", "rgba(255, 51, 102, 0.5)");
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-units-rule-cache-ref.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-units-rule-cache-ref.html
new file mode 100644
index 0000000..6c82619
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-units-rule-cache-ref.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<style>
+  .container {
+    width: 100px;
+  }
+
+  .half {
+    height: 50%;
+    background-color: green;
+  }
+</style>
+<div class="container" style="height: 100px">
+  <div class="half"></div>
+</div>
+<div class="container" style="height: 200px">
+  <div class="half"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-units-rule-cache.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-units-rule-cache.html
new file mode 100644
index 0000000..cc93f77
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-units-rule-cache.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="help" href="https://drafts.csswg.org/css-contain-3/#container-lengths">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1832481">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="container-units-rule-cache-ref.html">
+<style>
+  .container {
+    width: 100px;
+    container-type: size;
+  }
+
+  .half {
+    height: 50cqh;
+    background-color: green;
+  }
+</style>
+<div class="container" style="height: 100px">
+  <div class="half"></div>
+</div>
+<div class="container" style="height: 200px">
+  <div class="half"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering-2.html.ini b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering-2.html.ini
deleted file mode 100644
index 722e601..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering-2.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-set-negative-resolution-rendering-2.html]
-  expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering.html.ini b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering.html.ini
deleted file mode 100644
index a5b715b..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-negative-resolution-rendering.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-set-negative-resolution-rendering.html]
-  expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-parsing-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-parsing-expected.txt
deleted file mode 100644
index db23524..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-parsing-expected.txt
+++ /dev/null
@@ -1,134 +0,0 @@
-This is a testharness.js-based test.
-Found 130 tests; 122 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS e.style['background-image'] = "image-set(url(example.png) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set('example.jpg' 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set('example.jpg' 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) 1x, 'example.png' 2x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1x, 'example.png' 2x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) 1dpcm, 'example.png' 2x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1dpcm, 'example.png' 2x)" should set the property value
-PASS e.style['background-image'] = "image-set('example.jpeg' 222dpi, url(example.png) 3.5x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set('example.jpeg' 222dpi, url(example.png) 3.5x)" should set the property value
-PASS e.style['content'] = "image-set(url(\"example.png\") 1x)" should set the property value
-PASS e.style['content'] = "-webkit-image-set(url(\"example.png\") 1x)" should set the property value
-PASS e.style['content'] = "image-set(url(\"example.png\") 1x, \"example.png\" 3x)" should set the property value
-PASS e.style['content'] = "-webkit-image-set(url(\"example.png\") 1x, \"example.png\" 3x)" should set the property value
-PASS e.style['border-image-source'] = "image-set(url(\"example.png\") 1x)" should set the property value
-PASS e.style['border-image-source'] = "-webkit-image-set(url(\"example.png\") 1x)" should set the property value
-PASS e.style['border-image-source'] = "image-set(url(\"example.png\") 1x, \"example.png\" 3x)" should set the property value
-PASS e.style['border-image-source'] = "-webkit-image-set(url(\"example.png\") 1x, \"example.png\" 3x)" should set the property value
-PASS e.style['background-image'] = "image-set(none, url(example.png) 1x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(none, url(example.png) 1x)" should not set the property value
-PASS e.style['background-image'] = "image-set()" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set()" should not set the property value
-PASS e.style['background-image'] = "image-set('example.jpeg' 92pid url(example.png) 1x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set('example.jpeg' 92pid url(example.png) 1x)" should not set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) 1x url(example.jpeg))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1x url(example.jpeg))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) 1x 2x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1x 2x)" should not set the property value
-PASS e.style['background-image'] = "image-set(url(foo))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(foo))" should set the property value
-PASS e.style['background-image'] = "image-set(url(foo), url(bar) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(foo), url(bar) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(foo) 1x, url(bar))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(foo) 1x, url(bar))" should set the property value
-PASS e.style['background-image'] = "image-set(url(foo), url(bar))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(foo), url(bar))" should set the property value
-PASS e.style['background-image'] = "image-set(url(foo) 1x, url(bar), url(baz) 2x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(foo) 1x, url(bar), url(baz) 2x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(2x * 3))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(2x * 3))" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1dppx)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1dppx)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(1dppx * 1))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(1dppx * 1))" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1dpi)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1dpi)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(96dpi * 2))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(96dpi * 2))" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1dpcm)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1dpcm)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(1dpcm * 96/2.54))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(1dpcm * 96/2.54))" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1x, url(\"example.png\") 2dppx, \"example.png\" 250dpi, \"example.png\" 1dpcm)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1x, url(\"example.png\") 2dppx, \"example.png\" 250dpi, \"example.png\" 1dpcm)" should set the property value
-PASS e.style['content'] = "image-set(url(\"example.png\") 1dpi)" should set the property value
-PASS e.style['content'] = "-webkit-image-set(url(\"example.png\") 1dpi)" should set the property value
-PASS e.style['content'] = "image-set(url(\"example.png\") calc(1 * 96dpi))" should set the property value
-PASS e.style['content'] = "-webkit-image-set(url(\"example.png\") calc(1 * 96dpi))" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1invalidResUnit)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1invalidResUnit)" should not set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(3 * 4))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(3 * 4))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(2 - 1))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(2 - 1))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(2x - 1))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(2x - 1))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") calc(1 + 4dpi))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") calc(1 + 4dpi))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 0x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 0x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 0dppx)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 0dppx)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 0dpi)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 0dpi)" should set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 0dpcm)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 0dpcm)" should set the property value
-FAIL e.style['background-image'] = "image-set(url(\"example.png\") -1x)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -1x)"
-FAIL e.style['background-image'] = "-webkit-image-set(url(\"example.png\") -1x)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -1x)"
-FAIL e.style['background-image'] = "image-set(url(\"example.png\") -3dppx)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -3dppx)"
-FAIL e.style['background-image'] = "-webkit-image-set(url(\"example.png\") -3dppx)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -3dppx)"
-FAIL e.style['background-image'] = "image-set(url(\"example.png\") -96dpi)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -96dpi)"
-FAIL e.style['background-image'] = "-webkit-image-set(url(\"example.png\") -96dpi)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -96dpi)"
-FAIL e.style['background-image'] = "image-set(url(\"example.png\") -113dpcm)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -113dpcm)"
-FAIL e.style['background-image'] = "-webkit-image-set(url(\"example.png\") -113dpcm)" should not set the property value assert_equals: expected "" but got "image-set(url(\"example.png\") -113dpcm)"
-PASS e.style['background-image'] = "image-set(linear-gradient(black, white) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(linear-gradient(black, white) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(repeating-linear-gradient(red, blue 25%) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(repeating-linear-gradient(red, blue 25%) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(radial-gradient(black, white) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(radial-gradient(black, white) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(repeating-radial-gradient(red, blue 25%) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(repeating-radial-gradient(red, blue 25%) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(conic-gradient(black, white) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(conic-gradient(black, white) 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(repeating-conic-gradient(red, blue 25%) 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(repeating-conic-gradient(red, blue 25%) 1x)" should set the property value
-PASS e.style['content'] = "image-set(linear-gradient(black, white) 1x, url(\"example.png\") 4x)" should set the property value
-PASS e.style['content'] = "-webkit-image-set(linear-gradient(black, white) 1x, url(\"example.png\") 4x)" should set the property value
-PASS e.style['content'] = "image-set(url(\"example.png\") 192dpi, linear-gradient(black, white) 1x)" should set the property value
-PASS e.style['content'] = "-webkit-image-set(url(\"example.png\") 192dpi, linear-gradient(black, white) 1x)" should set the property value
-PASS e.style['cursor'] = "image-set(linear-gradient(black, white) 1x)" should not set the property value
-PASS e.style['cursor'] = "-webkit-image-set(linear-gradient(black, white) 1x)" should not set the property value
-PASS e.style['background-image'] = "image-set(linear-gradient(red) 1x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(linear-gradient(red) 1x)" should not set the property value
-PASS e.style['background-image'] = "image-set(url(\"example.png\") 1x type(\"image/png\"))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(\"example.png\") 1x type(\"image/png\"))" should set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) type('image/png'))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) type('image/png'))" should set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) type('image/png') 1x)" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) type('image/png') 1x)" should set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) 1x type('image/jpeg'))" should set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1x type('image/jpeg'))" should set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) type(image/png))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) type(image/png))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) type('image/png') type('image/png'))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) type('image/png') type('image/png'))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) type('image/png' 'image/png'))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) type('image/png' 'image/png'))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) type(url('image/png')))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) type(url('image/png')))" should not set the property value
-PASS e.style['background-image'] = "image-set(url(example.png) 1xtype('image/png'))" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(url(example.png) 1xtype('image/png'))" should not set the property value
-PASS e.style['background-image'] = "image-set(type('image/png') url(example.png) 1x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(type('image/png') url(example.png) 1x)" should not set the property value
-PASS e.style['background-image'] = "image-set(image-set(url(example.png)) 2x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(image-set(url(example.png)) 2x)" should not set the property value
-PASS e.style['background-image'] = "image-set(image(image-set(url(example.png)) 2x) 2x)" should not set the property value
-PASS e.style['background-image'] = "-webkit-image-set(image(image-set(url(example.png)) 2x) 2x)" should not set the property value
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-parsing.html.ini b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-parsing.html.ini
deleted file mode 100644
index ac11ff01..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-parsing.html.ini
+++ /dev/null
@@ -1,24 +0,0 @@
-[image-set-parsing.html]
-  [e.style['background-image'\] = "-webkit-image-set(url(\\"example.png\\") -113dpcm)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "-webkit-image-set(url(\\"example.png\\") -1x)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "-webkit-image-set(url(\\"example.png\\") -3dppx)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "-webkit-image-set(url(\\"example.png\\") -96dpi)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "image-set(url(\\"example.png\\") -113dpcm)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "image-set(url(\\"example.png\\") -1x)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "image-set(url(\\"example.png\\") -3dppx)" should not set the property value]
-    expected: FAIL
-
-  [e.style['background-image'\] = "image-set(url(\\"example.png\\") -96dpi)" should not set the property value]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html
index 7e6ea8f..eafe5bfb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html
@@ -8,8 +8,8 @@
 <style>
   #test {
     background-image: image-set(
-      url("/images/green.png") 1x type('image/png'),
-      url("/images/red.png") 1x type('image/png')
+      url("/images/green.png") 0.0001x type('image/png'),
+      url("/images/red.png") 0.0001x type('image/png')
     );
     width: 100px;
     height: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/clip-path-path-interpolation-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-001-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/clip-path-path-interpolation-001-ref.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-001-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-001.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-001.html
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-001.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-001.html
index 9b12621..0c988e09 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-001.html
@@ -3,7 +3,7 @@
 <head>
   <title>CSS Masking: Test clip-path nonzero path interpolation</title>
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
-  <link rel="match" href="reference/clip-path-path-interpolation-001-ref.html">
+  <link rel="match" href="clip-path-path-interpolation-001-ref.html">
   <meta name="assert" content="The clip-path property takes the basic shape
 	'path()' for clipping. Test the interpolation of nonzero
         path function.">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/clip-path-path-interpolation-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-002-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/clip-path-path-interpolation-002-ref.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-002-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-002.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-002.html
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-002.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-002.html
index 4cf6fb4a0..4c1c485f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-002.html
@@ -3,7 +3,7 @@
 <head>
   <title>CSS Masking: Test clip-path evenodd path interpolation</title>
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
-  <link rel="match" href="reference/clip-path-path-interpolation-002-ref.html">
+  <link rel="match" href="clip-path-path-interpolation-002-ref.html">
   <meta name="assert" content="The clip-path property takes the basic shape
 	'path()' for clipping. Test the interpolation of evenodd
         path function.">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/clip-path-path-interpolation-with-zoom-ref.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-with-zoom-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/clip-path-path-interpolation-with-zoom-ref.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-with-zoom-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-with-zoom.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-with-zoom.html
similarity index 89%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-with-zoom.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-with-zoom.html
index 4d54708..50dc5e6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-path-interpolation-with-zoom.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-path-interpolation-with-zoom.html
@@ -2,7 +2,7 @@
 <html>
   <title>CSS Masking: Test clip-path nonzero path interpolation with zoom</title>
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
-  <link rel="match" href="reference/clip-path-path-interpolation-with-zoom-ref.html">
+  <link rel="match" href="clip-path-path-interpolation-with-zoom-ref.html">
   <meta name="assert" content="The clip-path property takes the basic shape
 	'path()' for clipping. Test the interpolation of nonzero
         path function.">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-001.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-001.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-001.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-001.html
index 1d881e5..dae7f24d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-001.html
@@ -3,7 +3,7 @@
 <head>
   <title>CSS Masking: Test clip-path nonzero path interpolation</title>
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
-  <link rel="match" href="reference/clip-path-path-interpolation-001-ref.html">
+  <link rel="match" href="clip-path-path-interpolation-001-ref.html">
   <meta name="assert" content="The clip-path property takes the basic shape
 	'shape()' for clipping. Test the interpolation of nonzero
         path function.">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-001.html.ini
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-001.html.ini
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-001.html.ini
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-002.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-002.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-002.html
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-002.html
index 9d8ab65..6af23c3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-002.html
@@ -3,7 +3,7 @@
 <head>
   <title>CSS Masking: Test clip-path evenodd path interpolation</title>
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
-  <link rel="match" href="reference/clip-path-path-interpolation-002-ref.html">
+  <link rel="match" href="clip-path-path-interpolation-002-ref.html">
   <meta name="assert" content="The clip-path property takes the basic shape
 	'shape()' for clipping. Test the interpolation of evenodd
         path function.">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-002.html.ini b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-002.html.ini
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-interpolation-002.html.ini
rename to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-shape-interpolation-002.html.ini
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting-ref.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting-ref.html
index 006216b..0057a67 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting-ref.html
@@ -5,8 +5,8 @@
 <style>
   .test {
     background-color: green;
-    width: 100px;
-    height: 100px;
+    width: 30px;
+    height: 30px;
     display: grid;
   }
 
@@ -21,4 +21,7 @@
   <div class="test"></div>
   <div class="test"></div>
   <div class="test"></div>
+  <div class="test"></div>
+  <div class="test"></div>
+  <div class="test"></div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting.html
index 05b9e04..0a76ded 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/implicit-nesting.html
@@ -6,8 +6,8 @@
 <style>
   .test {
     background-color: red;
-    width: 100px;
-    height: 100px;
+    width: 30px;
+    height: 30px;
     display: grid;
   }
 
@@ -41,12 +41,30 @@
     }
   }
 
-  .test {
+  .test-5 {
     :is(.test-5, &.does-not-exist) {
       background-color: green;
     }
   }
 
+  .test-6 {
+    > .foo,.test-6-child,+ .bar {
+      background-color: green;
+    }
+  }
+
+  .test-7 {
+    > .foo, .bar, + .test-7-sibling {
+      background-color: green;
+    }
+  }
+
+  .test-8 {
+    > .foo, .test-8-child, + .bar {
+      background-color: green;
+    }
+  }
+
   body * + * {
     margin-top: 8px;
   }
@@ -57,5 +75,8 @@
   <div class="test test-2"><div class="test-2-child"></div></div>
   <div class="test test-3"><div class="test-3-child"></div></div>
   <div class="test test-4"></div>
-  <div class="test test-5"></div>
+  <div class="test test-5"><div class="test-5"></div></div>
+  <div class="test test-6"><div class="test-6-child"></div></div>
+  <div class="test test-7" style="display:none"></div><div class="test test-7-sibling"></div>
+  <div class="test test-8"><div class="test-8-child"></div></div>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/parsing.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/parsing.html
index 8e445fa..c22eaada 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-nesting/parsing.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/parsing.html
@@ -19,6 +19,11 @@
     `.foo {\n  &.bar { color: green; }\n}`,
     `.foo {\n  & .bar { color: green; }\n}`,
     `.foo {\n  & > .bar { color: green; }\n}`,
+    `.foo {\n  > .bar { color: green; }\n}`,
+    `.foo {\n  > & .bar { color: green; }\n}`,
+    `.foo {\n  + .bar & { color: green; }\n}`,
+    `.foo {\n  .test > & .bar { color: green; }\n}`,
+    `.foo {\n  + .bar, .foo, > .lol { color: green; }\n}`,
     `.foo {\n  &:is(.bar, &.baz) { color: green; }\n}`,
     `.foo {\n  .bar& { color: green; }\n}`,
     `.foo {\n  .bar & { color: green; }\n}`,
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html
new file mode 100644
index 0000000..bb31f2b2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title> CSS Scroll Snap 2 Test: scroll-start-target-* computed values</title>
+  <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/css/support/computed-testcommon.js"></script>
+</head>
+
+<body>
+  <div id="target"></div>
+  <script>
+    test_computed_value("scroll-start-target-block", "auto");
+    test_computed_value("scroll-start-target-block", "none");
+
+    test_computed_value("scroll-start-target-inline", "auto");
+    test_computed_value("scroll-start-target-inline", "none");
+
+    test_computed_value("scroll-start-target-x", "auto");
+    test_computed_value("scroll-start-target-x", "none");
+
+    test_computed_value("scroll-start-target-y", "auto");
+    test_computed_value("scroll-start-target-y", "none");
+
+    target.style = "";
+
+    // Test logical-physical mapping.
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetBlock = "auto";
+      assert_equals(getComputedStyle(target).scrollStartTargetX, "none");
+      assert_equals(getComputedStyle(target).scrollStartTargetY, "auto");
+    }, "scroll-start-block maps to scroll-start-y in horizontal writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetInline = "auto";
+      assert_equals(getComputedStyle(target).scrollStartTargetX, "auto");
+      assert_equals(getComputedStyle(target).scrollStartTargetY, "none");
+    }, "scroll-start-inline maps to scroll-start-x in horizontal writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetX = "auto";
+      assert_equals(getComputedStyle(target).scrollStartTargetBlock, "none");
+      assert_equals(getComputedStyle(target).scrollStartTargetInline, "auto");
+    }, "scroll-start-x maps to scroll-start-inline in horizontal writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetY = "auto";
+      assert_equals(getComputedStyle(target).scrollStartTargetBlock, "auto");
+      assert_equals(getComputedStyle(target).scrollStartTargetInline, "none");
+    }, "scroll-start-y maps to scroll-start-block in horizontal writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetBlock = "auto";
+      target.style.writingMode = "vertical-lr";
+      assert_equals(getComputedStyle(target).scrollStartTargetX, "auto");
+      assert_equals(getComputedStyle(target).scrollStartTargetY, "none");
+    }, "scroll-start-block maps to scroll-start-x in vertical writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetInline = "auto";
+      target.style.writingMode = "vertical-lr";
+      assert_equals(getComputedStyle(target).scrollStartTargetX, "none");
+      assert_equals(getComputedStyle(target).scrollStartTargetY, "auto");
+    }, "scroll-start-inline maps to scroll-start-y in vertical writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetX = "auto";
+      target.style.writingMode = "vertical-lr";
+      assert_equals(getComputedStyle(target).scrollStartTargetBlock, "auto");
+      assert_equals(getComputedStyle(target).scrollStartTargetInline, "none");
+    }, "scroll-start-x maps to scroll-start-block in vertical writing mode.");
+    test((t) => {
+      t.add_cleanup(() => { target.style = ""; });
+      target.style.scrollStartTargetY = "auto";
+      target.style.writingMode = "vertical-lr";
+      assert_equals(getComputedStyle(target).scrollStartTargetBlock, "none");
+      assert_equals(getComputedStyle(target).scrollStartTargetInline, "auto");
+    }, "scroll-start-y maps to scroll-start-inline in vertical writing mode.");
+    </script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html
new file mode 100644
index 0000000..03ca718
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title> CSS Scroll Snap 2 Test: scroll-start-target-* with invalid values</title>
+  <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+  <script>
+    test_invalid_value("scroll-start-target", "invalid_keyword");
+    test_invalid_value("scroll-start-target", "100px");
+    test_invalid_value("scroll-start-target", "none none none");
+    test_invalid_value("scroll-start-target", "invalid_keyword1 invalid_keyword2");
+    test_invalid_value("scroll-start-target", "100px 100px");
+
+    test_invalid_value("scroll-start-target-block", "invalid_keyword");
+    test_invalid_value("scroll-start-target-block", "100px");
+    test_invalid_value("scroll-start-target-block", "none none");
+
+    test_invalid_value("scroll-start-target-inline", "invalid_keyword");
+    test_invalid_value("scroll-start-target-inline", "100px");
+    test_invalid_value("scroll-start-target-inline", "none none");
+
+    test_invalid_value("scroll-start-target-x", "invalid_keyword");
+    test_invalid_value("scroll-start-target-x", "100px");
+    test_invalid_value("scroll-start-target-x", "none none");
+
+    test_invalid_value("scroll-start-target-y", "invalid_keyword");
+    test_invalid_value("scroll-start-target-y", "100px");
+    test_invalid_value("scroll-start-target-y", "none none");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html
new file mode 100644
index 0000000..6017157
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Scroll Snap 2 Test: scroll-none-target sets longhands</title>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/shorthand-testcommon.js"></script>
+</head>
+
+<body>
+  <script>
+    test_shorthand_value("scroll-start-target", "none", {
+      "scroll-start-target-block": "none",
+      "scroll-start-target-inline": "none",
+    });
+    test_shorthand_value("scroll-start-target", "auto", {
+      "scroll-start-target-block": "auto",
+      "scroll-start-target-inline": "none",
+    });
+    test_shorthand_value("scroll-start-target", "none auto", {
+      "scroll-start-target-block": "none",
+      "scroll-start-target-inline": "auto",
+    });
+    test_shorthand_value("scroll-start-target", "auto none", {
+      "scroll-start-target-block": "auto",
+      "scroll-start-target-inline": "none",
+    });
+  </script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html
new file mode 100644
index 0000000..aed964bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title> CSS Scroll Snap 2 Test: scroll-start-target-* with valid values</title>
+  <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/css/support/parsing-testcommon.js"></script>
+</head>
+
+<body>
+  <script>
+    test_valid_value("scroll-start-target", "none");
+    test_valid_value("scroll-start-target", "auto");
+    test_valid_value("scroll-start-target", "none auto");
+    test_valid_value("scroll-start-target", "auto none", "auto");
+
+    test_valid_value("scroll-start-target-block", "none");
+    test_valid_value("scroll-start-target-block", "auto");
+
+    test_valid_value("scroll-start-target-inline", "none");
+    test_valid_value("scroll-start-target-inline", "auto");
+
+    test_valid_value("scroll-start-target-x", "none");
+    test_valid_value("scroll-start-target-x", "auto");
+
+    test_valid_value("scroll-start-target-y", "none");
+    test_valid_value("scroll-start-target-y", "auto");
+  </script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-margin-visibility-check.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-margin-visibility-check.html
index a072c142..b41ccb3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-margin-visibility-check.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-margin-visibility-check.html
@@ -35,7 +35,7 @@
 promise_test(async function() {
   scroller.scrollTo(0, 0);
   await new Promise(resolve => {
-      scroller.addEventListener("scroll", () => step_timeout(resolve, 0));
+      scroller.addEventListener("scroll", resolve, { once: true });
       document.getElementById("target").focus();
   });
   assert_true(scroller.scrollTop > 0, "Visibility check should not account for margin");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-target-margin-005.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-target-margin-005.html
index 5e4782f..d3b6027 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-target-margin-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/scroll-target-margin-005.html
@@ -25,7 +25,7 @@
 promise_test(async function() {
    document.scrollingElement.scrollTo(0, 20000);
    await new Promise(resolve => {
-     document.addEventListener("scroll", () => step_timeout(resolve, 0));
+     document.addEventListener("scroll", resolve, { once: true });
      document.getElementById("target").focus();
    });
   // Should be around 4900 (5000 - 200px of margin). Give some leeway to account for line height / borders / input padding / etc.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/rules-groups-ref.html b/third_party/blink/web_tests/external/wpt/css/css-tables/rules-groups-ref.html
new file mode 100644
index 0000000..8da4884
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-tables/rules-groups-ref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS reference: legacy table rules attribute</title>
+
+<style>
+table { margin: 1em 1em 2em 1em; border-collapse: collapse; }
+
+#a thead, #a tbody { border-block-end-width: 1px; border-block-end-style: solid; }
+#b thead, #b tbody { border-block-end: 1px solid blue; }
+#c thead, #c tbody { border-block-end-width: 5px; border-block-end-style: solid; }
+</style>
+
+<p>Borders between groups should be present:</p>
+<table id=a>
+ <thead><tr><td>head</td></tr></thead>
+ <tbody>
+   <tr><td>body</td><td>one</td></tr>
+   <tr><td>body</td><td>two</td></tr>
+ </tbody>
+ <tfoot><tr><td>foot</td></tr></tfoot>
+</table>
+
+<p>Borders between groups should be blue:</p>
+<table id=b>
+ <thead><tr><td>head</td></tr></thead>
+ <tbody>
+   <tr><td>body</td><td>one</td></tr>
+   <tr><td>body</td><td>two</td></tr>
+ </tbody>
+ <tfoot><tr><td>foot</td></tr></tfoot>
+</table>
+
+<p>Borders between groups should be 5px thick:</p>
+<table id=c>
+ <thead><tr><td>head</td></tr></thead>
+ <tbody>
+   <tr><td>body</td><td>one</td></tr>
+   <tr><td>body</td><td>two</td></tr>
+ </tbody>
+ <tfoot><tr><td>foot</td></tr></tfoot>
+</table>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/rules-groups.html b/third_party/blink/web_tests/external/wpt/css/css-tables/rules-groups.html
new file mode 100644
index 0000000..d07aa0b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-tables/rules-groups.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS Test: legacy table rules attribute</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jfkthame@mozilla.com">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1832101">
+<link rel="match" href="rules-groups-ref.html">
+
+<style>
+table { margin: 1em 1em 2em 1em; }
+
+/* Modify style of the inter-group borders, assuming default UA rules are in effect. */
+
+/* With border-block-start suppressed, the block-end border should still appear: */
+#a > * { border-block-start: none; }
+
+/* Change the color of the border: */
+#b > * { border-block-end-color: blue; }
+
+/* Change the thickness of the border: */
+#c > * { border-block-end-width: 5px; }
+</style>
+
+<p>Borders between groups should be present:</p>
+<table id=a rules=groups>
+ <thead><tr><td>head</td></tr></thead>
+ <tbody>
+   <tr><td>body</td><td>one</td></tr>
+   <tr><td>body</td><td>two</td></tr>
+ </tbody>
+ <tfoot><tr><td>foot</td></tr></tfoot>
+</table>
+
+<p>Borders between groups should be blue:</p>
+<table id=b rules=groups>
+ <thead><tr><td>head</td></tr></thead>
+ <tbody>
+   <tr><td>body</td><td>one</td></tr>
+   <tr><td>body</td><td>two</td></tr>
+ </tbody>
+ <tfoot><tr><td>foot</td></tr></tfoot>
+</table>
+
+<p>Borders between groups should be 5px thick:</p>
+<table id=c rules=groups>
+ <thead><tr><td>head</td></tr></thead>
+ <tbody>
+   <tr><td>body</td><td>one</td></tr>
+   <tr><td>body</td><td>two</td></tr>
+ </tbody>
+ <tfoot><tr><td>foot</td></tr></tfoot>
+</table>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-end.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-end.tentative-expected.txt
new file mode 100644
index 0000000..cd252b8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-end.tentative-expected.txt
@@ -0,0 +1,34 @@
+This is a testharness.js-based test.
+FAIL Can set 'animation-delay-end' to CSS-wide keywords: initial Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to CSS-wide keywords: inherit Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to CSS-wide keywords: unset Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to CSS-wide keywords: revert Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to var() references:  var(--A) Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to a time: 0s Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to a time: -3.14ms Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to a time: 3.14s Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+FAIL Can set 'animation-delay-end' to a time: calc(0s + 0ms) Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-end
+PASS Setting 'animation-delay-end' to a length: 0px throws TypeError
+PASS Setting 'animation-delay-end' to a length: -3.14em throws TypeError
+PASS Setting 'animation-delay-end' to a length: 3.14cm throws TypeError
+PASS Setting 'animation-delay-end' to a length: calc(0px + 0em) throws TypeError
+PASS Setting 'animation-delay-end' to a percent: 0% throws TypeError
+PASS Setting 'animation-delay-end' to a percent: -3.14% throws TypeError
+PASS Setting 'animation-delay-end' to a percent: 3.14% throws TypeError
+PASS Setting 'animation-delay-end' to a percent: calc(0% + 0%) throws TypeError
+PASS Setting 'animation-delay-end' to an angle: 0deg throws TypeError
+PASS Setting 'animation-delay-end' to an angle: 3.14rad throws TypeError
+PASS Setting 'animation-delay-end' to an angle: -3.14deg throws TypeError
+PASS Setting 'animation-delay-end' to an angle: calc(0rad + 0deg) throws TypeError
+PASS Setting 'animation-delay-end' to a flexible length: 0fr throws TypeError
+PASS Setting 'animation-delay-end' to a flexible length: 1fr throws TypeError
+PASS Setting 'animation-delay-end' to a flexible length: -3.14fr throws TypeError
+PASS Setting 'animation-delay-end' to a number: 0 throws TypeError
+PASS Setting 'animation-delay-end' to a number: -3.14 throws TypeError
+PASS Setting 'animation-delay-end' to a number: 3.14 throws TypeError
+PASS Setting 'animation-delay-end' to a number: calc(2 + 3) throws TypeError
+PASS Setting 'animation-delay-end' to a transform: translate(50%, 50%) throws TypeError
+PASS Setting 'animation-delay-end' to a transform: perspective(10em) throws TypeError
+PASS Setting 'animation-delay-end' to a transform: translate3d(0px, 1px, 2px) translate(0px, 1px) rotate3d(1, 2, 3, 45deg) rotate(45deg) scale3d(1, 2, 3) scale(1, 2) skew(1deg, 1deg) skewX(1deg) skewY(45deg) perspective(1px) matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) matrix(1, 2, 3, 4, 5, 6) throws TypeError
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-expected.txt
deleted file mode 100644
index 236b5bbb..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-expected.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-This is a testharness.js-based test.
-FAIL Can set 'animation-delay' to CSS-wide keywords: initial assert_equals: expected "CSSKeywordValue" but got "CSSStyleValue"
-FAIL Can set 'animation-delay' to CSS-wide keywords: inherit assert_equals: expected "CSSKeywordValue" but got "CSSStyleValue"
-FAIL Can set 'animation-delay' to CSS-wide keywords: unset assert_equals: expected "CSSKeywordValue" but got "CSSStyleValue"
-FAIL Can set 'animation-delay' to CSS-wide keywords: revert assert_equals: expected "CSSKeywordValue" but got "CSSStyleValue"
-FAIL Can set 'animation-delay' to var() references:  var(--A) assert_equals: expected "CSSUnparsedValue" but got "CSSStyleValue"
-FAIL Can set 'animation-delay' to a time: 0s Failed to execute 'set' on 'StylePropertyMap': Invalid type for property
-FAIL Can set 'animation-delay' to a time: -3.14ms Failed to execute 'set' on 'StylePropertyMap': Invalid type for property
-FAIL Can set 'animation-delay' to a time: 3.14s Failed to execute 'set' on 'StylePropertyMap': Invalid type for property
-FAIL Can set 'animation-delay' to a time: calc(0s + 0ms) Failed to execute 'set' on 'StylePropertyMap': Invalid type for property
-PASS Setting 'animation-delay' to a length: 0px throws TypeError
-PASS Setting 'animation-delay' to a length: -3.14em throws TypeError
-PASS Setting 'animation-delay' to a length: 3.14cm throws TypeError
-PASS Setting 'animation-delay' to a length: calc(0px + 0em) throws TypeError
-PASS Setting 'animation-delay' to a percent: 0% throws TypeError
-PASS Setting 'animation-delay' to a percent: -3.14% throws TypeError
-PASS Setting 'animation-delay' to a percent: 3.14% throws TypeError
-PASS Setting 'animation-delay' to a percent: calc(0% + 0%) throws TypeError
-PASS Setting 'animation-delay' to an angle: 0deg throws TypeError
-PASS Setting 'animation-delay' to an angle: 3.14rad throws TypeError
-PASS Setting 'animation-delay' to an angle: -3.14deg throws TypeError
-PASS Setting 'animation-delay' to an angle: calc(0rad + 0deg) throws TypeError
-PASS Setting 'animation-delay' to a flexible length: 0fr throws TypeError
-PASS Setting 'animation-delay' to a flexible length: 1fr throws TypeError
-PASS Setting 'animation-delay' to a flexible length: -3.14fr throws TypeError
-PASS Setting 'animation-delay' to a number: 0 throws TypeError
-PASS Setting 'animation-delay' to a number: -3.14 throws TypeError
-PASS Setting 'animation-delay' to a number: 3.14 throws TypeError
-PASS Setting 'animation-delay' to a number: calc(2 + 3) throws TypeError
-PASS Setting 'animation-delay' to a transform: translate(50%, 50%) throws TypeError
-PASS Setting 'animation-delay' to a transform: perspective(10em) throws TypeError
-PASS Setting 'animation-delay' to a transform: translate3d(0px, 1px, 2px) translate(0px, 1px) rotate3d(1, 2, 3, 45deg) rotate(45deg) scale3d(1, 2, 3) scale(1, 2) skew(1deg, 1deg) skewX(1deg) skewY(45deg) perspective(1px) matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) matrix(1, 2, 3, 4, 5, 6) throws TypeError
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-start.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-start.tentative-expected.txt
new file mode 100644
index 0000000..2ec7b3a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/animation-delay-start.tentative-expected.txt
@@ -0,0 +1,34 @@
+This is a testharness.js-based test.
+FAIL Can set 'animation-delay-start' to CSS-wide keywords: initial Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to CSS-wide keywords: inherit Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to CSS-wide keywords: unset Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to CSS-wide keywords: revert Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to var() references:  var(--A) Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to a time: 0s Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to a time: -3.14ms Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to a time: 3.14s Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+FAIL Can set 'animation-delay-start' to a time: calc(0s + 0ms) Failed to execute 'set' on 'StylePropertyMap': Invalid propertyName: animation-delay-start
+PASS Setting 'animation-delay-start' to a length: 0px throws TypeError
+PASS Setting 'animation-delay-start' to a length: -3.14em throws TypeError
+PASS Setting 'animation-delay-start' to a length: 3.14cm throws TypeError
+PASS Setting 'animation-delay-start' to a length: calc(0px + 0em) throws TypeError
+PASS Setting 'animation-delay-start' to a percent: 0% throws TypeError
+PASS Setting 'animation-delay-start' to a percent: -3.14% throws TypeError
+PASS Setting 'animation-delay-start' to a percent: 3.14% throws TypeError
+PASS Setting 'animation-delay-start' to a percent: calc(0% + 0%) throws TypeError
+PASS Setting 'animation-delay-start' to an angle: 0deg throws TypeError
+PASS Setting 'animation-delay-start' to an angle: 3.14rad throws TypeError
+PASS Setting 'animation-delay-start' to an angle: -3.14deg throws TypeError
+PASS Setting 'animation-delay-start' to an angle: calc(0rad + 0deg) throws TypeError
+PASS Setting 'animation-delay-start' to a flexible length: 0fr throws TypeError
+PASS Setting 'animation-delay-start' to a flexible length: 1fr throws TypeError
+PASS Setting 'animation-delay-start' to a flexible length: -3.14fr throws TypeError
+PASS Setting 'animation-delay-start' to a number: 0 throws TypeError
+PASS Setting 'animation-delay-start' to a number: -3.14 throws TypeError
+PASS Setting 'animation-delay-start' to a number: 3.14 throws TypeError
+PASS Setting 'animation-delay-start' to a number: calc(2 + 3) throws TypeError
+PASS Setting 'animation-delay-start' to a transform: translate(50%, 50%) throws TypeError
+PASS Setting 'animation-delay-start' to a transform: perspective(10em) throws TypeError
+PASS Setting 'animation-delay-start' to a transform: translate3d(0px, 1px, 2px) translate(0px, 1px) rotate3d(1, 2, 3, 45deg) rotate(45deg) scale3d(1, 2, 3) scale(1, 2) skew(1deg, 1deg) skewX(1deg) skewY(45deg) perspective(1px) matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) matrix(1, 2, 3, 4, 5, 6) throws TypeError
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-image-repeat-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-image-repeat-001.html.ini
index 57160a0..fc0838c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-image-repeat-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-image-repeat-001.html.ini
@@ -1,3 +1,4 @@
 [kind-of-widget-fallback-input-reset-border-image-repeat-001.html]
   expected:
+    if (product == "content_shell") and (os == "linux"): FAIL
     if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-block-start-color-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-block-start-color-001.html.ini
index 476d602..faaa5d20 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-block-start-color-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-block-start-color-001.html.ini
@@ -1,3 +1,3 @@
 [kind-of-widget-fallback-input-submit-border-block-start-color-001.html]
   expected:
-    if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL
+    if (product == "content_shell") and (os == "win"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-inline-end-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-inline-end-style-001.html.ini
new file mode 100644
index 0000000..2555c731
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-inline-end-style-001.html.ini
@@ -0,0 +1,3 @@
+[kind-of-widget-fallback-textarea-border-inline-end-style-001.html]
+  expected:
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution-expected.txt
new file mode 100644
index 0000000..fa5daba
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution-expected.txt
@@ -0,0 +1,32 @@
+This is a testharness.js-based test.
+PASS 'image-set(url("") calc(1x * NaN))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1x * nan))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * NaN))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * infinity / infinity))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * 0 * infinity))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * (infinity + -infinity)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * (-infinity + infinity)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * (infinity - infinity)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * infinity))' as a specified value should serialize as 'image-set(url("") calc(infinity * 1dppx))'.
+FAIL 'image-set(url("") calc(1dppx * -infinity))' as a specified value should serialize as 'image-set(url("") calc(-infinity * 1dppx))'. assert_not_equals: 'image-set(url("") calc(1dppx * -infinity))' should be valid in backgroundImage. got disallowed value ""
+PASS 'image-set(url("") calc(1dppx * iNFinIty))' as a specified value should serialize as 'image-set(url("") calc(infinity * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * (infinity + infinity)))' as a specified value should serialize as 'image-set(url("") calc(infinity * 1dppx))'.
+FAIL 'image-set(url("") calc(1dppx * (-infinity + -infinity)))' as a specified value should serialize as 'image-set(url("") calc(-infinity * 1dppx))'. assert_not_equals: 'image-set(url("") calc(1dppx * (-infinity + -infinity)))' should be valid in backgroundImage. got disallowed value ""
+PASS 'image-set(url("") calc(1dppx * 1/infinity))' as a specified value should serialize as 'image-set(url("") calc(0dppx))'.
+PASS 'image-set(url("") calc(1dppx * infinity * infinity))' as a specified value should serialize as 'image-set(url("") calc(infinity * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * -infinity * -infinity))' as a specified value should serialize as 'image-set(url("") calc(infinity * 1dppx))'.
+PASS 'image-set(url("") calc(1 * max(INFinity*3dppx, 0dppx)))' as a specified value should serialize as 'image-set(url("") calc(infinity * 1dppx))'.
+PASS 'image-set(url("") calc(1 * min(inFInity*4dppx, 0dppx)))' as a specified value should serialize as 'image-set(url("") calc(0dppx))'.
+PASS 'image-set(url("") calc(1 * max(nAn*2dppx, 0dppx)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1 * min(nan*3dppx, 0dppx)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1 * clamp(-INFINITY*20dppx, 0dppx, infiniTY*10dppx)))' as a specified value should serialize as 'image-set(url("") calc(0dppx))'.
+PASS 'image-set(url("") calc(1dppx * max(NaN, min(0,10))))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * clamp(NaN, 0, 10)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * max(0, min(10, NaN))))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * clamp(0, 10, NaN)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * max(0, min(NaN, 10))))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * clamp(0, NaN, 10)))' as a specified value should serialize as 'image-set(url("") calc(NaN * 1dppx))'.
+PASS 'image-set(url("") calc(1dppx * clamp(-Infinity, 0, infinity)))' as a specified value should serialize as 'image-set(url("") calc(0dppx))'.
+PASS 'image-set(url("") calc(1dppx * clamp(-inFinity, infinity, 10)))' as a specified value should serialize as 'image-set(url("") calc(10dppx))'.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution.html.ini b/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution.html.ini
index 26ac406..9fba4d7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-infinity-nan-serialize-resolution.html.ini
@@ -1,3 +1,6 @@
 [calc-infinity-nan-serialize-resolution.html]
-  ['image-set(url("") calc(1 * clamp(-INFINITY*0dppx, 0dppx, infiniTY*0dppx)))' as a specified value should serialize as 'image-set(url("") calc(0dppx))'.]
+  ['image-set(url("") calc(1dppx * (-infinity + -infinity)))' as a specified value should serialize as 'image-set(url("") calc(-infinity * 1dppx))'.]
+    expected: FAIL
+
+  ['image-set(url("") calc(1dppx * -infinity))' as a specified value should serialize as 'image-set(url("") calc(-infinity * 1dppx))'.]
     expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-path-interpolation-006.html b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-path-interpolation-006.html
new file mode 100644
index 0000000..a19908e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-path-interpolation-006.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>offset-distance basic-shape interpolation</title>
+    <link rel="author" title="Daniil Sakhapov" href="mailto:sakhapov@chromium.org">
+    <link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
+    <meta name="assert" content="offset-path basic shape supports animation.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/css/support/interpolation-testcommon.js"></script>
+  </head>
+  <style>
+    .parent {
+      offset-path: ellipse(10% 10%);
+    }
+    .target {
+      offset-path: circle(10px);
+    }
+  </style>
+  <body>
+    <script>
+      'use strict';
+
+      test_no_interpolation({
+        property: 'offset-path',
+        from: "circle(10px)",
+        to: "inset(20px)",
+      });
+
+      test_no_interpolation({
+        property: 'offset-path',
+        from: "ellipse(at center)",
+        to: "none",
+      });
+
+      // Neutral keyframes use the inline style.
+      test_interpolation({
+        property: 'offset-path',
+        from: neutralKeyframe,
+        to: 'circle(20px)',
+      }, [
+        {at: -0.3, expect: 'circle(7px)'},
+        {at: 0, expect: 'circle(10px)'},
+        {at: 0.3, expect: 'circle(13px)'},
+        {at: 0.6, expect: 'circle(16px)'},
+        {at: 1, expect: 'circle(20px)'},
+        {at: 1.5, expect: 'circle(25px)'},
+      ]);
+
+      // No interpolation to an ellipse from the initial value 'none'.
+      test_no_interpolation({
+        property: 'offset-path',
+        from: 'initial',
+        to: 'ellipse()',
+      });
+
+      // 'inherit' keyframes use the parent style.
+      test_interpolation({
+        property: 'offset-path',
+        from: 'inherit',
+        to: 'ellipse(40% 50% at 25% 25%)',
+      }, [
+        {at: -0.3, expect: 'ellipse(1% 0% at 57.5% 57.5%)'},
+        {at: 0, expect: 'ellipse(10% 10% at 50% 50%)'},
+        {at: 0.3, expect: 'ellipse(19% 22% at 42.5% 42.5%)'},
+        {at: 0.6, expect: 'ellipse(28% 34% at 35% 35%)'},
+        {at: 1, expect: 'ellipse(40% 50% at 25% 25%)'},
+        {at: 1.5, expect: 'ellipse(55% 70% at 12.5% 12.5%)'},
+      ]);
+
+      // No interpolation to an inset from the initial value 'none'.
+      test_no_interpolation({
+        property: 'offset-path',
+        from: 'unset',
+        to: 'inset(10%)',
+      });
+
+      // No interpolation to a rect from the initial value 'none'.
+      test_no_interpolation({
+        property: 'offset-path',
+        from: 'none',
+        to: 'rect(10px 10px 10px 10px)',
+      });
+
+      // Interpolation between shapes.
+      test_interpolation({
+        property: 'offset-path',
+        from: 'inset(10px)',
+        to: 'inset(20px round 50%)'
+      }, [
+        {at: -1, expect: 'inset(0px round 0%)'},
+        {at: 0, expect: 'inset(10px round 0%)'},
+        {at: 0.125, expect: 'inset(11.25px round 6.25%)'},
+        {at: 0.875, expect: 'inset(18.75px round 43.75%)'},
+        {at: 1, expect: 'inset(20px round 50%)'},
+        {at: 2, expect: 'inset(30px round 100%)'},
+      ]);
+
+      test_interpolation({
+        property: 'offset-path',
+        from: 'xywh(5px 5px 150% 150%)',
+        to: 'xywh(10px 10px 100% 100%)'
+      }, [
+        {at: -1, expect: 'xywh(0px 0px 200% 200%)'},
+        {at: 0, expect: 'xywh(5px 5px 150% 150%)'},
+        {at: 0.125, expect: 'xywh(5.63px 5.63px 143.75% 143.75% )'},
+        {at: 0.875, expect: 'xywh(9.38px 9.38px 106.25% 106.25%)'},
+        {at: 1, expect: 'xywh(10px 10px 100% 100%)'},
+        {at: 2, expect: 'xywh(15px 15px 50% 50%)'},
+      ]);
+
+      // No interpolation between different radius keywords.
+      test_no_interpolation({
+        property: 'offset-path',
+        from: 'circle(farthest-side)',
+        to: 'circle(closest-side)'
+      });
+      test_no_interpolation({
+        property: 'offset-path',
+        from: 'ellipse(closest-side farthest-side)',
+        to: 'ellipse(closest-side closest-side)'
+      });
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/editing/crashtests/inserthtml-in-li-in-option.html b/third_party/blink/web_tests/external/wpt/editing/crashtests/inserthtml-in-li-in-option.html
new file mode 100644
index 0000000..c8aa9f7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/crashtests/inserthtml-in-li-in-option.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+"use strict";
+document.addEventListener("DOMContentLoaded", () => {
+  const select = document.querySelector("select");
+  select.appendChild(document.querySelector("option"));
+  select.appendChild(document.createElement("optgroup"));
+  document.querySelector("p").appendChild(document.querySelector("li[contenteditable]"));
+  getSelection().collapse(document.querySelector("output"), 0);
+  document.execCommand("insertHTML", false, select.innerHTML);
+});
+</script>
+</head>
+<body>
+<dl>
+<select>a</select>
+<p></p>
+<option>
+<li contenteditable>
+<output>
+</output><li></option></dl></body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker-expected.txt b/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker-expected.txt
deleted file mode 100644
index cfc8da4..0000000
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS There can only be one open access handle at any given time
-PASS An access handle from one file does not interfere with the creation of an access handle on another file
-PASS A writable stream from one file does not interfere with the creation of an access handle on another file
-PASS An access handle from one file does not interfere with the creation of a writable stream on another file
-FAIL Writable streams cannot be created if there is an open access handle promise_test: Unhandled rejection with value: object "ReferenceError: cleanup_writable is not defined"
-PASS Access handles cannot be created if there are open Writable streams
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js b/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js
index 1b7fcda..dbecc24 100644
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js
+++ b/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js
@@ -1,5 +1,6 @@
 importScripts('/resources/testharness.js');
 importScripts('resources/sandboxed-fs-test-helpers.js');
+importScripts('resources/test-helpers.js');
 
 'use strict';
 
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js.ini b/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js.ini
deleted file mode 100644
index 5c367a2..0000000
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.html]
-  [Writable streams cannot be created if there is an open access handle]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/no-browsing-context.window-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/no-browsing-context.window-expected.txt
deleted file mode 100644
index 12dc8bc..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/no-browsing-context.window-expected.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-This is a testharness.js-based test.
-PASS Window and Location are 1:1 after browsing context removal
-FAIL Setting `href` to `https://example.com/` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Setting `href` to `/` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Setting `href` to `http://test:test/` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Setting `href` to `test test` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Setting `href` to `test:test` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Setting `href` to `chrome:fail` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Setting `protocol` to `http` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:" but got (undefined) undefined
-FAIL Setting `protocol` to `about` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:" but got (undefined) undefined
-FAIL Setting `protocol` to `test` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:" but got (undefined) undefined
-FAIL Setting `host` to `example.com` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `host` to `test test` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `host` to `()` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `hostname` to `example.com` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `port` to `80` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `port` to `` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `port` to `443` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `port` to `notaport` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `pathname` to `/` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "blank" but got (undefined) undefined
-FAIL Setting `pathname` to `x` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "blank" but got (undefined) undefined
-FAIL Setting `search` to `test` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `hash` to `test` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Setting `hash` to `#` of a `Location` object sans browsing context is a no-op assert_equals: expected (string) "" but got (undefined) undefined
-FAIL Getting `origin` of a `Location` object sans browsing context should be "null" assert_equals: expected (string) "null" but got (undefined) undefined
-FAIL Invoking `assign` with `about:blank` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `assign` with `https://example.com/` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `assign` with `/` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `assign` with `http://test:test/` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `assign` with `test test` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `assign` with `test:test` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `assign` with `chrome:fail` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `replace` with `about:blank` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `replace` with `https://example.com/` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `replace` with `/` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `replace` with `http://test:test/` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `replace` with `test test` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `replace` with `test:test` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `replace` with `chrome:fail` on a `Location` object sans browsing context is a no-op assert_equals: expected (string) "about:blank" but got (undefined) undefined
-FAIL Invoking `reload` with `about:blank` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `reload` with `https://example.com/` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `reload` with `/` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `reload` with `http://test:test/` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `reload` with `test test` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `reload` with `test:test` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Invoking `reload` with `chrome:fail` on a `Location` object sans browsing context is a no-op loc[method] is not a function
-FAIL Getting `ancestorOrigins` of a `Location` object sans browsing context should be [] assert_array_equals: value is undefined, expected array
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/windows/document-domain-removed-iframe-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/windows/document-domain-removed-iframe-expected.txt
deleted file mode 100644
index 5ae578d..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/windows/document-domain-removed-iframe-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-PASS No removal, no document.domain
-PASS Removal, no document.domain
-PASS No removal, document.domain
-FAIL Removal, document.domain assert_equals: expected (string) "" but got (undefined) undefined
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html
new file mode 100644
index 0000000..8428d0cf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<title>
+  Verify that we consider browsing context group reuse for COOP reporting.
+</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/cross-origin-opener-policy/reporting/resources/reporting-common.js"></script>
+<script src="/html/cross-origin-opener-policy/reporting/resources/try-access.js"></script>
+<script src="/html/cross-origin-opener-policy/resources/common.js"></script>
+<script>
+
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+promise_test(async test => {
+  const driver_token = token(); // For this window.
+  const report_token = token();
+  const reportTo = reportToHeaders(report_token);
+
+  // 1. Start by opening a first window that will act as the opener in this test
+  // scenario. It sets COOP-RO: restrict-properties.
+  const opener_token = token(); // The current test window.
+  const opener_url = same_origin + executor_path + reportTo.header +
+      reportTo.coopReportOnlyRestrictPropertiesHeader +
+      `&uuid=${opener_token}`;
+  const opener = window.open(opener_url);
+  test.add_cleanup(() => send(opener_token, "window.close()"));
+
+  // 2. From the opener, open a popup without any COOP. It should be in a
+  // different virtual browsing context group.
+  const initial_openee_token = token();
+  const initial_openee_url = cross_origin + executor_path +
+      `&uuid=${initial_openee_token}`;
+  send(opener_token, `
+    window.openee = window.open('${initial_openee_url}');
+  `);
+  test.add_cleanup(() => send(opener_token, "window.close()"));
+
+  // 3. Navigate the openee to a COOP-RO: restrict-properties page. If the
+  // policy was enforced, it would live in the same browsing context group as
+  // the opener. The virtual browsing context group should similarly be equal.
+  // Note: We omit the reporting endpoint header, because it is not possible to
+  // easily escape it. Since it is not necessary in this test, we skip it.
+  const final_openee_token = token();
+  const final_openee_url = same_origin + executor_path +
+      reportTo.coopReportOnlyRestrictPropertiesHeader +
+      `&uuid=${final_openee_token}`;
+
+  send(initial_openee_token, `location.href = '${final_openee_url}';`);
+  test.add_cleanup(() => send(final_openee_token, "window.close()"));
+
+  // Wait for the final openee to load.
+  send(final_openee_token,
+    `send("${driver_token}", "Ready");
+  `);
+  assert_equals(await receive(driver_token), "Ready");
+
+  // 4. Try to access the openee from the opener. No report should be sent.
+  send(opener_token, addScriptAndTriggerOnload(
+    "/html/cross-origin-opener-policy/reporting/resources/try-access.js",
+    "tryAccess(window.openee);")
+  );
+
+  let report =
+    await receiveReport(report_token, "access-from-coop-page-to-openee")
+  assert_equals(report, "timeout");
+
+}, "access-reporting-browsing-context-group-reuse");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
index d66d455..93baf65 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
@@ -167,7 +167,7 @@
   document.body.appendChild(outViewPortButton);
 
   await new Promise(resolve => {
-    document.addEventListener("scroll", () => step_timeout(resolve, 0));
+    document.addEventListener("scroll", resolve, { once: true });
     outViewPortButton.focus();
   });
 
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window-expected.txt
deleted file mode 100644
index 1e6f712..0000000
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-This is a testharness.js-based test.
-PASS document.open() changes document's URL (fully active document)
-FAIL document.open() does not change document's URL (active but not fully active document) assert_equals: expected (string) "http://web-platform.test:8001/common/blank.html" but got (undefined) undefined
-PASS document.open() does not change document's URL (non-active document with an associated Window object; frame is removed)
-PASS document.open() does not change document's URL (non-active document with an associated Window object; navigated away)
-PASS document.open() does not change document's URL (non-active document without an associated Window object)
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html b/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html
deleted file mode 100644
index dd946e4..0000000
--- a/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<link rel="modulepreload" href="resources/module1.js?empty-string" as="" data-as="">
-<link rel="modulepreload" href="resources/module1.js?audio" as="audio" data-as="audio">
-<link rel="modulepreload" href="resources/module1.js?audioworklet" as="audioworklet" data-as="audioworklet">
-<link rel="modulepreload" href="resources/module1.js?document" as="document" data-as="document">
-<link rel="modulepreload" href="resources/module1.js?embed" as="embed" data-as="embed">
-<link rel="modulepreload" href="resources/module1.js?font" as="font" data-as="font">
-<link rel="modulepreload" href="resources/module1.js?frame" as="frame" data-as="frame">
-<link rel="modulepreload" href="resources/module1.js?iframe" as="iframe" data-as="iframe">
-<link rel="modulepreload" href="resources/module1.js?image" as="image" data-as="image">
-<link rel="modulepreload" href="resources/module1.js?manifest" as="manifest" data-as="manifest">
-<link rel="modulepreload" href="resources/module1.js?object" as="object" data-as="object">
-<link rel="modulepreload" href="resources/module1.js?paintworklet" as="paintworklet" data-as="paintworklet">
-<link rel="modulepreload" href="resources/module1.js?report" as="report" data-as="report">
-<link rel="modulepreload" href="resources/module1.js?script" as="script" data-as="script">
-<link rel="modulepreload" href="resources/module1.js?serviceworker" as="serviceworker" data-as="serviceworker">
-<link rel="modulepreload" href="resources/module1.js?sharedworker" as="sharedworker" data-as="sharedworker">
-<link rel="modulepreload" href="resources/module1.js?style" as="style" data-as="style">
-<link rel="modulepreload" href="resources/module1.js?track" as="track" data-as="track">
-<link rel="modulepreload" href="resources/module1.js?video" as="video" data-as="video">
-<link rel="modulepreload" href="resources/module1.js?webidentity" as="webidentity" data-as="webidentity">
-<link rel="modulepreload" href="resources/module1.js?worker" as="worker" data-as="worker">
-<link rel="modulepreload" href="resources/module1.js?xslt" as="xslt" data-as="xslt">
-<link rel="modulepreload" href="resources/module1.js?fetch" as="fetch" data-as="fetch">
-<link rel="modulepreload" href="resources/module1.js?invalid-dest" as="invalid-dest" data-as="invalid-dest">
-<link rel="modulepreload" href="resources/module1.js?iMaGe" as="iMaGe" data-as="iMaGe">
-<link rel="modulepreload" href="resources/module1.js?sCrIpT" as="sCrIpT" data-as="sCrIpT">
-<body>
-<script>
-  // compared to modulepreload.html, this tests behavior when elements are
-  // initially on an HTML page instead of being added by JS
-
-  const scriptLikes = [
-    'audioworklet',
-    'paintworklet',
-    'script',
-    'serviceworker',
-    'sharedworker',
-    'worker',
-  ];
-
-  const goodAsValues = ['', 'invalid-dest', 'sCrIpT', ...scriptLikes];
-
-  for (const link of document.querySelectorAll('link')) {
-    const asValue = link.dataset.as; // don't depend on "as" attribute reflection
-    const good = goodAsValues.includes(asValue);
-
-    // promise tests are queued sequentially, so create the promise here to
-    // ensure we don't miss the error event
-    const promise = new Promise((resolve, reject) => {
-      link.onload = good ? resolve : reject;
-      link.onerror = good ? reject : resolve;
-    });
-
-    promise_test(() => promise.then(() => {
-      const downloads = performance
-        .getEntriesByName(new URL(link.href, location.href))
-        .filter(entry => entry.transferSize > 0)
-        .length;
-      assert_equals(downloads, good ? 1 : 0);
-
-    }), `Modulepreload with as="${asValue}"`);
-  }
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html.ini b/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html.ini
deleted file mode 100644
index 9e9e21f..0000000
--- a/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html.ini
+++ /dev/null
@@ -1,147 +0,0 @@
-[modulepreload-as.html]
-  expected:
-    if (product == "content_shell") and (os == "win") and (port == "win11"): TIMEOUT
-    if (product == "content_shell") and (os == "mac") and (port == "mac12"): OK
-    if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): OK
-    if (product == "content_shell") and (os == "mac") and (port == "mac11"): OK
-    ERROR
-  [Modulepreload with as="audio"]
-    expected: FAIL
-
-  [Modulepreload with as="audioworklet"]
-    expected: FAIL
-
-  [Modulepreload with as="document"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      FAIL
-
-  [Modulepreload with as="embed"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      FAIL
-
-  [Modulepreload with as="fetch"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-
-  [Modulepreload with as="font"]
-    expected:
-      if (product == "content_shell") and (os == "linux") and (flag_specific == ""): PASS
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "win"): PASS
-      FAIL
-
-  [Modulepreload with as="frame"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "win"): PASS
-      if product == "chrome": PASS
-      FAIL
-
-  [Modulepreload with as="iMaGe"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-
-  [Modulepreload with as="iframe"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): PASS
-      if (product == "content_shell") and (os == "win"): PASS
-      if product == "chrome": PASS
-      FAIL
-
-  [Modulepreload with as="image"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-
-  [Modulepreload with as="invalid-dest"]
-    expected: FAIL
-
-  [Modulepreload with as="manifest"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-
-  [Modulepreload with as="object"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [PASS, FAIL]
-
-  [Modulepreload with as="paintworklet"]
-    expected: FAIL
-
-  [Modulepreload with as="report"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-
-  [Modulepreload with as="serviceworker"]
-    expected: FAIL
-
-  [Modulepreload with as="sharedworker"]
-    expected: FAIL
-
-  [Modulepreload with as="style"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "linux") and (flag_specific == ""): PASS
-      FAIL
-
-  [Modulepreload with as="track"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "linux"): PASS
-      if product == "chrome": PASS
-      FAIL
-
-  [Modulepreload with as="video"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-
-  [Modulepreload with as="webidentity"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-
-  [Modulepreload with as="worker"]
-    expected: FAIL
-
-  [Modulepreload with as="xslt"]
-    expected:
-      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
-      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
-      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
-      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-expected.txt b/third_party/blink/web_tests/external/wpt/preload/modulepreload-expected.txt
deleted file mode 100644
index 1cef0af..0000000
--- a/third_party/blink/web_tests/external/wpt/preload/modulepreload-expected.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-This is a testharness.js-based test.
-PASS link rel=modulepreload
-PASS same-origin link rel=modulepreload crossorigin=anonymous
-PASS same-origin link rel=modulepreload crossorigin=use-credentials
-PASS cross-origin link rel=modulepreload
-PASS cross-origin link rel=modulepreload crossorigin=anonymous
-PASS cross-origin link rel=modulepreload crossorigin=use-credentials
-PASS link rel=modulepreload with submodules
-PASS link rel=modulepreload for a module with syntax error
-PASS link rel=modulepreload for a module with network error
-PASS link rel=modulepreload with bad href attribute
-PASS link rel=modulepreload as=script
-PASS link rel=modulepreload with non-script-like as= value (image)
-PASS link rel=modulepreload with non-script-like as= value (xslt)
-PASS link rel=modulepreload with integrity match
-PASS link rel=modulepreload with integrity match2
-PASS link rel=modulepreload with integrity mismatch
-PASS link rel=modulepreload with integrity mismatch2
-FAIL link rel=modulepreload with integrity mismatch3 promise_test: Unhandled rejection with value: object "[object Event]"
-PASS multiple link rel=modulepreload with same href
-PASS multiple link rel=modulepreload with child module before parent
-PASS link rel=modulepreload with matching media
-PASS link rel=modulepreload with non-matching media
-PASS link rel=modulepreload with empty media
-PASS link rel=modulepreload with empty href
-PASS link rel=modulepreload with empty href and invalid as= value
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-sri.html b/third_party/blink/web_tests/external/wpt/preload/modulepreload-sri.html
deleted file mode 100644
index ea32a6a..0000000
--- a/third_party/blink/web_tests/external/wpt/preload/modulepreload-sri.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<link rel="modulepreload" href="resources/module1.js" integrity="sha384-invalid">
-<script type="module" src="resources/module1.js" id="myscript"></script>
-<body>
-<script>
-  // compared to modulepreload.html, this tests behavior when elements are
-  // initially on an HTML page instead of being added by JS
-  promise_test(() => {
-    return new Promise((resolve, reject) => {
-      let myscript = document.querySelector('#myscript');
-      myscript.onerror = resolve;
-      myscript.onload = reject;
-    });
-  }, "Script should not be loaded if modulepreload's integrity is invalid");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload.html b/third_party/blink/web_tests/external/wpt/preload/modulepreload.html
index bcd18c89..0e4b692 100644
--- a/third_party/blink/web_tests/external/wpt/preload/modulepreload.html
+++ b/third_party/blink/web_tests/external/wpt/preload/modulepreload.html
@@ -33,15 +33,6 @@
     });
 }
 
-function attachAndWaitForTimeout(element, t) {
-    return new Promise((resolve, reject) => {
-        element.onload = reject;
-        element.onerror = reject;
-        t.step_timeout(resolve, 1000);
-        document.body.appendChild(element);
-    });
-}
-
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
@@ -223,21 +214,13 @@
     link.href = 'resources/module1.js?as-image';
     link.as = 'image'
     return attachAndWaitForError(link);
-}, 'link rel=modulepreload with non-script-like as= value (image)');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = 'resources/module1.js?as-xslt';
-    link.as = 'xslt'
-    return attachAndWaitForError(link);
-}, 'link rel=modulepreload with non-script-like as= value (xslt)');
+}, 'link rel=modulepreload with invalid as= value');
 
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
     link.href = 'resources/module1.js?integrity-match';
-    link.integrity = 'sha256-+Ks3iNIiTq2ujlWhvB056cmXobrCFpU9hd60xZ1WCaA='
+    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
     return attachAndWaitForLoad(link);
 }, 'link rel=modulepreload with integrity match');
 
@@ -245,7 +228,7 @@
     var link = document.createElement('link');
     link.rel = 'modulepreload';
     link.href = 'resources/module1.mjs?integrity-match';
-    link.integrity = 'sha256-+Ks3iNIiTq2ujlWhvB056cmXobrCFpU9hd60xZ1WCaA='
+    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
     return attachAndWaitForLoad(link);
 }, 'link rel=modulepreload with integrity match2');
 
@@ -257,88 +240,5 @@
     return attachAndWaitForError(link);
 }, 'link rel=modulepreload with integrity mismatch');
 
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = 'resources/module1.mjs?integrity-doesnotmatch';
-    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc='
-    return attachAndWaitForError(link);
-}, 'link rel=modulepreload with integrity mismatch2');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = 'resources/module1.mjs?integrity-invalid';
-    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
-    return attachAndWaitForError(link);
-}, 'link rel=modulepreload with integrity mismatch3');
-
-promise_test(function(t) {
-    var link1 = document.createElement('link');
-    var link2 = document.createElement('link');
-    link1.rel = 'modulepreload';
-    link2.rel = 'modulepreload';
-    link1.href = 'resources/module1.js?same-url';
-    link2.href = 'resources/module1.js?same-url';
-    return Promise.all([
-        attachAndWaitForLoad(link1),
-        attachAndWaitForLoad(link2),
-    ]);
-}, 'multiple link rel=modulepreload with same href');
-
-promise_test(function(t) {
-    var link1 = document.createElement('link');
-    var link2 = document.createElement('link');
-    link1.rel = 'modulepreload';
-    link2.rel = 'modulepreload';
-    link1.href = 'resources/module2.js?child-before';
-    link2.href = 'resources/module1.js?child-before';
-    return attachAndWaitForLoad(link1)
-        .then(() => attachAndWaitForLoad(link2))
-        .then(() => new Promise(r => t.step_timeout(r, 1000)))
-        .then(() => {
-            verifyNumberOfDownloads('resources/module2.js?child-before', 1);
-        });
-
-}, 'multiple link rel=modulepreload with child module before parent');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = 'resources/module1.mjs?matching-media';
-    link.media = 'all';
-    return attachAndWaitForLoad(link);
-}, 'link rel=modulepreload with matching media');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = 'resources/module1.mjs?non-matching-media';
-    link.media = 'not all';
-    return attachAndWaitForTimeout(link, t);
-}, 'link rel=modulepreload with non-matching media');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = 'resources/module1.mjs?empty-media';
-    link.media = '';
-    return attachAndWaitForLoad(link);
-}, 'link rel=modulepreload with empty media');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = '';
-    return attachAndWaitForTimeout(link, t);
-}, 'link rel=modulepreload with empty href');
-
-promise_test(function(t) {
-    var link = document.createElement('link');
-    link.rel = 'modulepreload';
-    link.href = '';
-    link.as = 'fetch';
-    return attachAndWaitForTimeout(link, t);
-}, 'link rel=modulepreload with empty href and invalid as= value');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-shorthand-expected.txt b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-shorthand-expected.txt
new file mode 100644
index 0000000..5b9ea08
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-shorthand-expected.txt
@@ -0,0 +1,30 @@
+This is a testharness.js-based test.
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim" should set the property value
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim initial" should not set the property value
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim 2000" should not set the property value
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim scroll()" should not set the property value
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim view()" should not set the property value
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim timeline" should not set the property value
+PASS Property animation value '1s linear 1s 2 reverse forwards paused anim'
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-delay
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-direction
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-duration
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-fill-mode
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-iteration-count
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-name
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-play-state
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-range-end
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-range-start
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-timeline
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should set animation-timing-function
+PASS e.style['animation'] = "1s linear 1s 2 reverse forwards paused anim1,\n   1s linear 1s 2 reverse forwards paused anim2,\n   1s linear 1s 2 reverse forwards paused anim3" should not set unrelated longhands
+PASS Animation shorthand can not represent non-initial timelines (specified)
+PASS Animation shorthand can not represent non-initial timelines (computed)
+FAIL Animation shorthand can not represent non-initial animation-delay-end (specified) assert_equals: expected "" but got "1s ease 0s 1 normal none running anim"
+FAIL Animation shorthand can not represent non-initial animation-delay-end (computed) assert_equals: expected "" but got "1s ease 0s 1 normal none running anim"
+PASS Animation shorthand can not represent non-initial animation-range-start (specified)
+PASS Animation shorthand can not represent non-initial animation-range-start (computed)
+PASS Animation shorthand can not represent non-initial animation-range-end (specified)
+PASS Animation shorthand can not represent non-initial animation-range-end (computed)
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-1.html b/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-1.html
index 6863456..e8bd262 100644
--- a/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-1.html
+++ b/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-1.html
@@ -8,5 +8,6 @@
 <pre id=target contenteditable>ABC
 <br></pre>
 <script>
+  target.focus();
   getSelection().collapse(target.childNodes[0], 4);
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-2.html b/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-2.html
index ac119cb..0feee46 100644
--- a/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-2.html
+++ b/third_party/blink/web_tests/external/wpt/selection/caret/collapse-pre-linestart-2.html
@@ -9,5 +9,6 @@
 
 </pre>
 <script>
+  target.focus();
   getSelection().collapse(target.childNodes[0], 4);
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/detached-context.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/detached-context.https.html
index 747a953..ce8e4cc 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/detached-context.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/detached-context.https.html
@@ -119,23 +119,6 @@
     }
     assert_not_equals(get_navigator().serviceWorker, null);
     iframe.remove();
-    assert_throws_js(TypeError, () => get_navigator());
-  }, 'accessing navigator on a removed frame');
-
-// It seems weird that about:blank and blank.html (the test above) have
-// different behavior. These expectations are based on Chromium behavior, which
-// might not be right.
-test(t => {
-    const iframe = document.createElement('iframe');
-    iframe.src = 'about:blank';
-    document.body.appendChild(iframe);
-    const f = iframe.contentWindow.Function;
-    function get_navigator() {
-      return f('return navigator')();
-    }
     assert_not_equals(get_navigator().serviceWorker, null);
-    iframe.remove();
-    assert_equals(get_navigator().serviceWorker, null);
-  }, 'accessing navigator.serviceWorker on a removed about:blank frame');
-
+  }, 'accessing navigator on a removed frame');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html.ini b/third_party/blink/web_tests/external/wpt/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html.ini
new file mode 100644
index 0000000..c43d33d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html.ini
@@ -0,0 +1,16 @@
+[reuse-web-bundle-resource.https.tentative.html]
+  ['remove(), then append() in a separate task' should not reuse webbundle resources]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [PASS, FAIL]
+
+  ['remove(), then append()' should reuse webbundle resources]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [PASS, FAIL]
+
+  [A webbundle should be fetched again when new script element is appended.]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [FAIL, PASS]
+
+  [replaceWith() should reuse webbundle resources.]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta-expected.txt b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta-expected.txt
deleted file mode 100644
index 229c339..0000000
--- a/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-PASS Test import.meta.url on the top-level module script.
-PASS Test import.meta.url on the imported module script.
-FAIL Test import.meta.url on the imported module script with a fragment. assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta.html b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta.html
index cff8e91..97a5da8 100644
--- a/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta.html
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-meta.html
@@ -53,7 +53,7 @@
         worker.postMessage('./' + script_url + '#1');
         return new Promise(resolve => worker.onmessage = resolve);
       })
-      .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+      .then(msg_event => assert_true(msg_event.data.endsWith(script_url + "#1")));
 }, 'Test import.meta.url on the imported module script with a fragment.');
 
 </script>
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
index b9df6de..31e6531 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
@@ -54,8 +54,7 @@
 anchor-name: none
 anchor-scroll: none
 animation-composition: replace
-animation-delay-end: 0s
-animation-delay-start: 0s
+animation-delay: 0s
 animation-direction: normal
 animation-duration: auto
 animation-fill-mode: none
@@ -313,6 +312,10 @@
 scroll-padding-inline-start: auto
 scroll-start-block: auto
 scroll-start-inline: auto
+scroll-start-target-block: none
+scroll-start-target-inline: none
+scroll-start-target-x: none
+scroll-start-target-y: none
 scroll-start-x: auto
 scroll-start-y: auto
 scroll-timeline-attachment: local
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
index af91557..48002f2 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
@@ -54,8 +54,7 @@
 anchor-name: none
 anchor-scroll: none
 animation-composition: replace
-animation-delay-end: 0s
-animation-delay-start: 0s
+animation-delay: 0s
 animation-direction: normal
 animation-duration: auto
 animation-fill-mode: none
@@ -313,6 +312,10 @@
 scroll-padding-inline-start: auto
 scroll-start-block: auto
 scroll-start-inline: auto
+scroll-start-target-block: none
+scroll-start-target-inline: none
+scroll-start-target-x: none
+scroll-start-target-y: none
 scroll-start-x: auto
 scroll-start-y: auto
 scroll-timeline-attachment: local
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index 4e4016d7..4693c16 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -5,15 +5,15 @@
 PASS window.cached_cookieStore.onchange is null
 PASS window.cached_documentPictureInPicture.onenter is null
 PASS window.cached_documentPictureInPicture.window is null
-PASS window.cached_location.hash is undefined
-PASS window.cached_location.host is undefined
-PASS window.cached_location.hostname is undefined
-PASS window.cached_location.href is undefined
-PASS window.cached_location.origin is undefined
-PASS window.cached_location.pathname is undefined
-PASS window.cached_location.port is undefined
-PASS window.cached_location.protocol is undefined
-PASS window.cached_location.search is undefined
+PASS window.cached_location.hash is ''
+PASS window.cached_location.host is ''
+PASS window.cached_location.hostname is ''
+PASS window.cached_location.href is 'about:blank'
+PASS window.cached_location.origin is 'null'
+PASS window.cached_location.pathname is 'blank'
+PASS window.cached_location.port is ''
+PASS window.cached_location.protocol is 'about:'
+PASS window.cached_location.search is ''
 FAIL window.cached_location_ancestorOrigins.length should be 0. Was 1.
 PASS window.cached_locationbar.visible is false
 PASS window.cached_menubar.visible is false
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index 0eda0b8c..c4e5c8b5 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -5,15 +5,15 @@
 PASS window.cached_cookieStore.onchange is null
 PASS window.cached_documentPictureInPicture.onenter is null
 PASS window.cached_documentPictureInPicture.window is null
-PASS window.cached_location.hash is undefined
-PASS window.cached_location.host is undefined
-PASS window.cached_location.hostname is undefined
-PASS window.cached_location.href is undefined
-PASS window.cached_location.origin is undefined
-PASS window.cached_location.pathname is undefined
-PASS window.cached_location.port is undefined
-PASS window.cached_location.protocol is undefined
-PASS window.cached_location.search is undefined
+PASS window.cached_location.hash is ''
+PASS window.cached_location.host is ''
+PASS window.cached_location.hostname is ''
+PASS window.cached_location.href is 'about:blank'
+PASS window.cached_location.origin is 'null'
+PASS window.cached_location.pathname is 'blank'
+PASS window.cached_location.port is ''
+PASS window.cached_location.protocol is 'about:'
+PASS window.cached_location.search is ''
 FAIL window.cached_location_ancestorOrigins.length should be 0. Was 1.
 PASS window.cached_locationbar.visible is false
 PASS window.cached_menubar.visible is false
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index d1206f8..8c612f77 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -5,15 +5,15 @@
 PASS window.cached_cookieStore.onchange is null
 PASS window.cached_documentPictureInPicture.onenter is null
 PASS window.cached_documentPictureInPicture.window is null
-PASS window.cached_location.hash is undefined
-PASS window.cached_location.host is undefined
-PASS window.cached_location.hostname is undefined
-PASS window.cached_location.href is undefined
-PASS window.cached_location.origin is undefined
-PASS window.cached_location.pathname is undefined
-PASS window.cached_location.port is undefined
-PASS window.cached_location.protocol is undefined
-PASS window.cached_location.search is undefined
+PASS window.cached_location.hash is ''
+PASS window.cached_location.host is ''
+PASS window.cached_location.hostname is ''
+PASS window.cached_location.href is 'about:blank'
+PASS window.cached_location.origin is 'null'
+PASS window.cached_location.pathname is 'blank'
+PASS window.cached_location.port is ''
+PASS window.cached_location.protocol is 'about:'
+PASS window.cached_location.search is ''
 FAIL window.cached_location_ancestorOrigins.length should be 0. Was 1.
 PASS window.cached_locationbar.visible is false
 PASS window.cached_menubar.visible is false
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index 8b26569..795e793 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -13,7 +13,16 @@
 PASS childWindow.innerWidth is 0
 PASS childWindow.isSecureContext is true
 PASS childWindow.length is 0
-PASS childWindow.location.href is undefined
+PASS childWindow.location.ancestorOrigins.length is 0
+PASS childWindow.location.hash is ''
+PASS childWindow.location.host is ''
+PASS childWindow.location.hostname is ''
+PASS childWindow.location.href is 'about:blank'
+PASS childWindow.location.origin is 'null'
+PASS childWindow.location.pathname is 'blank'
+PASS childWindow.location.port is ''
+PASS childWindow.location.protocol is 'about:'
+PASS childWindow.location.search is ''
 PASS childWindow.locationbar.visible is false
 PASS childWindow.menubar.visible is false
 PASS childWindow.name is ''
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index 66caf55a..ca8c9a4 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -13,7 +13,16 @@
 PASS childWindow.innerWidth is 0
 PASS childWindow.isSecureContext is true
 PASS childWindow.length is 0
-PASS childWindow.location.href is undefined
+PASS childWindow.location.ancestorOrigins.length is 0
+PASS childWindow.location.hash is ''
+PASS childWindow.location.host is ''
+PASS childWindow.location.hostname is ''
+PASS childWindow.location.href is 'about:blank'
+PASS childWindow.location.origin is 'null'
+PASS childWindow.location.pathname is 'blank'
+PASS childWindow.location.port is ''
+PASS childWindow.location.protocol is 'about:'
+PASS childWindow.location.search is ''
 PASS childWindow.locationbar.visible is false
 PASS childWindow.menubar.visible is false
 PASS childWindow.name is ''
diff --git a/third_party/blink/web_tests/fast/dom/Window/resources/window-property-collector.js b/third_party/blink/web_tests/fast/dom/Window/resources/window-property-collector.js
index 7727e06..40f2268 100644
--- a/third_party/blink/web_tests/fast/dom/Window/resources/window-property-collector.js
+++ b/third_party/blink/web_tests/fast/dom/Window/resources/window-property-collector.js
@@ -110,18 +110,21 @@
         expected = "'unsafe-none'";
         break;
 
-    // TODO(dcheng): Figure out why these become undefined...
-    case "location.hash":
-    case "location.host":
-    case "location.hostname":
+    // location's url is left intact on detach. The location getters will
+    // provide the appropriate components of our test url (about:blank).
     case "location.href":
-    case "location.origin":
-    case "location.pathname":
-    case "location.port":
-    case "location.protocol":
-    case "location.search":
-        expected = "undefined";
+        expected = "'about:blank'";
         break;
+    case "location.origin":
+        expected = "'null'";
+        break;
+    case "location.pathname":
+        expected = "'blank'";
+        break;
+    case "location.protocol":
+        expected = "'about:'";
+        break;
+
     case "navigator.mediaSession.playbackState":
         expected = "'none'";
         break;
diff --git a/third_party/blink/web_tests/fast/wasm/wasm-constants.js b/third_party/blink/web_tests/fast/wasm/wasm-constants.js
deleted file mode 100644
index bc2e35d..0000000
--- a/third_party/blink/web_tests/fast/wasm/wasm-constants.js
+++ /dev/null
@@ -1,376 +0,0 @@
-// Copyright 2017 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Flags: --expose-wasm
-
-function bytes() {
-  var buffer = new ArrayBuffer(arguments.length);
-  var view = new Uint8Array(buffer);
-  for (var i = 0; i < arguments.length; i++) {
-    var val = arguments[i];
-    if ((typeof val) == "string") val = val.charCodeAt(0);
-    view[i] = val | 0;
-  }
-  return buffer;
-}
-
-// Header declaration constants
-var kWasmH0 = 0;
-var kWasmH1 = 0x61;
-var kWasmH2 = 0x73;
-var kWasmH3 = 0x6d;
-
-var kWasmV0 = 0x1;
-var kWasmV1 = 0;
-var kWasmV2 = 0;
-var kWasmV3 = 0;
-
-var kHeaderSize = 8;
-var kPageSize = 65536;
-
-function bytesWithHeader() {
-  var buffer = new ArrayBuffer(kHeaderSize + arguments.length);
-  var view = new Uint8Array(buffer);
-  view[0] = kWasmH0;
-  view[1] = kWasmH1;
-  view[2] = kWasmH2;
-  view[3] = kWasmH3;
-  view[4] = kWasmV0;
-  view[5] = kWasmV1;
-  view[6] = kWasmV2;
-  view[7] = kWasmV3;
-  for (var i = 0; i < arguments.length; i++) {
-    var val = arguments[i];
-    if ((typeof val) == "string") val = val.charCodeAt(0);
-    view[kHeaderSize + i] = val | 0;
-  }
-  return buffer;
-}
-
-let kDeclNoLocals = 0;
-
-// Section declaration constants
-let kUnknownSectionCode = 0;
-let kTypeSectionCode = 1;      // Function signature declarations
-let kImportSectionCode = 2;    // Import declarations
-let kFunctionSectionCode = 3;  // Function declarations
-let kTableSectionCode = 4;     // Indirect function table and other tables
-let kMemorySectionCode = 5;    // Memory attributes
-let kGlobalSectionCode = 6;    // Global declarations
-let kExportSectionCode = 7;    // Exports
-let kStartSectionCode = 8;     // Start function declaration
-let kElementSectionCode = 9;  // Elements section
-let kCodeSectionCode = 10;      // Function code
-let kDataSectionCode = 11;     // Data segments
-let kNameSectionCode = 12;     // Name section (encoded as string)
-
-let kWasmFunctionTypeForm = 0x60;
-let kWasmAnyFunctionTypeForm = 0x70;
-
-let kResizableMaximumFlag = 1;
-
-// Function declaration flags
-let kDeclFunctionName   = 0x01;
-let kDeclFunctionImport = 0x02;
-let kDeclFunctionLocals = 0x04;
-let kDeclFunctionExport = 0x08;
-
-// Local types
-let kWasmStmt = 0x40;
-let kWasmI32 = 0x7f;
-let kWasmI64 = 0x7e;
-let kWasmF32 = 0x7d;
-let kWasmF64 = 0x7c;
-let kWasmS128 = 0x7b;
-
-let kExternalFunction = 0;
-let kExternalTable = 1;
-let kExternalMemory = 2;
-let kExternalGlobal = 3;
-
-let kTableZero = 0;
-let kMemoryZero = 0;
-
-// Useful signatures
-let kSig_i_i = makeSig([kWasmI32], [kWasmI32]);
-let kSig_l_l = makeSig([kWasmI64], [kWasmI64]);
-let kSig_i_l = makeSig([kWasmI64], [kWasmI32]);
-let kSig_i_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32]);
-let kSig_i_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]);
-let kSig_d_dd = makeSig([kWasmF64, kWasmF64], [kWasmF64]);
-let kSig_l_ll = makeSig([kWasmI64, kWasmI64], [kWasmI64]);
-let kSig_i_dd = makeSig([kWasmF64, kWasmF64], [kWasmI32]);
-let kSig_v_v = makeSig([], []);
-let kSig_i_v = makeSig([], [kWasmI32]);
-let kSig_l_v = makeSig([], [kWasmI64]);
-let kSig_f_v = makeSig([], [kWasmF64]);
-let kSig_d_v = makeSig([], [kWasmF64]);
-let kSig_v_i = makeSig([kWasmI32], []);
-let kSig_v_ii = makeSig([kWasmI32, kWasmI32], []);
-let kSig_v_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], []);
-let kSig_v_l = makeSig([kWasmI64], []);
-let kSig_v_d = makeSig([kWasmF64], []);
-let kSig_v_dd = makeSig([kWasmF64, kWasmF64], []);
-let kSig_v_ddi = makeSig([kWasmF64, kWasmF64, kWasmI32], []);
-let kSig_s_v = makeSig([], [kWasmS128]);
-
-function makeSig(params, results) {
-  return {params: params, results: results};
-}
-
-function makeSig_v_x(x) {
-  return makeSig([x], []);
-}
-
-function makeSig_v_xx(x) {
-  return makeSig([x, x], []);
-}
-
-function makeSig_r_v(r) {
-  return makeSig([], [r]);
-}
-
-function makeSig_r_x(r, x) {
-  return makeSig([x], [r]);
-}
-
-function makeSig_r_xx(r, x) {
-  return makeSig([x, x], [r]);
-}
-
-// Opcodes
-let kExprUnreachable = 0x00;
-let kExprNop = 0x01;
-let kExprBlock = 0x02;
-let kExprLoop = 0x03;
-let kExprIf = 0x04;
-let kExprElse = 0x05;
-let kExprTry = 0x06;
-let kExprCatch = 0x07;
-let kExprThrow = 0x08;
-let kExprEnd = 0x0b;
-let kExprBr = 0x0c;
-let kExprBrIf = 0x0d;
-let kExprBrTable = 0x0e;
-let kExprReturn = 0x0f;
-let kExprCallFunction = 0x10;
-let kExprCallIndirect = 0x11;
-let kExprDrop = 0x1a;
-let kExprSelect = 0x1b;
-let kExprGetLocal = 0x20;
-let kExprSetLocal = 0x21;
-let kExprTeeLocal = 0x22;
-let kExprGetGlobal = 0x23;
-let kExprSetGlobal = 0x24;
-let kExprI32Const = 0x41;
-let kExprI64Const = 0x42;
-let kExprF32Const = 0x43;
-let kExprF64Const = 0x44;
-let kExprI32LoadMem = 0x28;
-let kExprI64LoadMem = 0x29;
-let kExprF32LoadMem = 0x2a;
-let kExprF64LoadMem = 0x2b;
-let kExprI32LoadMem8S = 0x2c;
-let kExprI32LoadMem8U = 0x2d;
-let kExprI32LoadMem16S = 0x2e;
-let kExprI32LoadMem16U = 0x2f;
-let kExprI64LoadMem8S = 0x30;
-let kExprI64LoadMem8U = 0x31;
-let kExprI64LoadMem16S = 0x32;
-let kExprI64LoadMem16U = 0x33;
-let kExprI64LoadMem32S = 0x34;
-let kExprI64LoadMem32U = 0x35;
-let kExprI32StoreMem = 0x36;
-let kExprI64StoreMem = 0x37;
-let kExprF32StoreMem = 0x38;
-let kExprF64StoreMem = 0x39;
-let kExprI32StoreMem8 = 0x3a;
-let kExprI32StoreMem16 = 0x3b;
-let kExprI64StoreMem8 = 0x3c;
-let kExprI64StoreMem16 = 0x3d;
-let kExprI64StoreMem32 = 0x3e;
-let kExprMemorySize = 0x3f;
-let kExprGrowMemory = 0x40;
-let kExprI32Eqz = 0x45;
-let kExprI32Eq = 0x46;
-let kExprI32Ne = 0x47;
-let kExprI32LtS = 0x48;
-let kExprI32LtU = 0x49;
-let kExprI32GtS = 0x4a;
-let kExprI32GtU = 0x4b;
-let kExprI32LeS = 0x4c;
-let kExprI32LeU = 0x4d;
-let kExprI32GeS = 0x4e;
-let kExprI32GeU = 0x4f;
-let kExprI64Eqz = 0x50;
-let kExprI64Eq = 0x51;
-let kExprI64Ne = 0x52;
-let kExprI64LtS = 0x53;
-let kExprI64LtU = 0x54;
-let kExprI64GtS = 0x55;
-let kExprI64GtU = 0x56;
-let kExprI64LeS = 0x57;
-let kExprI64LeU = 0x58;
-let kExprI64GeS = 0x59;
-let kExprI64GeU = 0x5a;
-let kExprF32Eq = 0x5b;
-let kExprF32Ne = 0x5c;
-let kExprF32Lt = 0x5d;
-let kExprF32Gt = 0x5e;
-let kExprF32Le = 0x5f;
-let kExprF32Ge = 0x60;
-let kExprF64Eq = 0x61;
-let kExprF64Ne = 0x62;
-let kExprF64Lt = 0x63;
-let kExprF64Gt = 0x64;
-let kExprF64Le = 0x65;
-let kExprF64Ge = 0x66;
-let kExprI32Clz = 0x67;
-let kExprI32Ctz = 0x68;
-let kExprI32Popcnt = 0x69;
-let kExprI32Add = 0x6a;
-let kExprI32Sub = 0x6b;
-let kExprI32Mul = 0x6c;
-let kExprI32DivS = 0x6d;
-let kExprI32DivU = 0x6e;
-let kExprI32RemS = 0x6f;
-let kExprI32RemU = 0x70;
-let kExprI32And = 0x71;
-let kExprI32Ior = 0x72;
-let kExprI32Xor = 0x73;
-let kExprI32Shl = 0x74;
-let kExprI32ShrS = 0x75;
-let kExprI32ShrU = 0x76;
-let kExprI32Rol = 0x77;
-let kExprI32Ror = 0x78;
-let kExprI64Clz = 0x79;
-let kExprI64Ctz = 0x7a;
-let kExprI64Popcnt = 0x7b;
-let kExprI64Add = 0x7c;
-let kExprI64Sub = 0x7d;
-let kExprI64Mul = 0x7e;
-let kExprI64DivS = 0x7f;
-let kExprI64DivU = 0x80;
-let kExprI64RemS = 0x81;
-let kExprI64RemU = 0x82;
-let kExprI64And = 0x83;
-let kExprI64Ior = 0x84;
-let kExprI64Xor = 0x85;
-let kExprI64Shl = 0x86;
-let kExprI64ShrS = 0x87;
-let kExprI64ShrU = 0x88;
-let kExprI64Rol = 0x89;
-let kExprI64Ror = 0x8a;
-let kExprF32Abs = 0x8b;
-let kExprF32Neg = 0x8c;
-let kExprF32Ceil = 0x8d;
-let kExprF32Floor = 0x8e;
-let kExprF32Trunc = 0x8f;
-let kExprF32NearestInt = 0x90;
-let kExprF32Sqrt = 0x91;
-let kExprF32Add = 0x92;
-let kExprF32Sub = 0x93;
-let kExprF32Mul = 0x94;
-let kExprF32Div = 0x95;
-let kExprF32Min = 0x96;
-let kExprF32Max = 0x97;
-let kExprF32CopySign = 0x98;
-let kExprF64Abs = 0x99;
-let kExprF64Neg = 0x9a;
-let kExprF64Ceil = 0x9b;
-let kExprF64Floor = 0x9c;
-let kExprF64Trunc = 0x9d;
-let kExprF64NearestInt = 0x9e;
-let kExprF64Sqrt = 0x9f;
-let kExprF64Add = 0xa0;
-let kExprF64Sub = 0xa1;
-let kExprF64Mul = 0xa2;
-let kExprF64Div = 0xa3;
-let kExprF64Min = 0xa4;
-let kExprF64Max = 0xa5;
-let kExprF64CopySign = 0xa6;
-let kExprI32ConvertI64 = 0xa7;
-let kExprI32SConvertF32 = 0xa8;
-let kExprI32UConvertF32 = 0xa9;
-let kExprI32SConvertF64 = 0xaa;
-let kExprI32UConvertF64 = 0xab;
-let kExprI64SConvertI32 = 0xac;
-let kExprI64UConvertI32 = 0xad;
-let kExprI64SConvertF32 = 0xae;
-let kExprI64UConvertF32 = 0xaf;
-let kExprI64SConvertF64 = 0xb0;
-let kExprI64UConvertF64 = 0xb1;
-let kExprF32SConvertI32 = 0xb2;
-let kExprF32UConvertI32 = 0xb3;
-let kExprF32SConvertI64 = 0xb4;
-let kExprF32UConvertI64 = 0xb5;
-let kExprF32ConvertF64 = 0xb6;
-let kExprF64SConvertI32 = 0xb7;
-let kExprF64UConvertI32 = 0xb8;
-let kExprF64SConvertI64 = 0xb9;
-let kExprF64UConvertI64 = 0xba;
-let kExprF64ConvertF32 = 0xbb;
-let kExprI32ReinterpretF32 = 0xbc;
-let kExprI64ReinterpretF64 = 0xbd;
-let kExprF32ReinterpretI32 = 0xbe;
-let kExprF64ReinterpretI64 = 0xbf;
-
-let kTrapUnreachable          = 0;
-let kTrapMemOutOfBounds       = 1;
-let kTrapDivByZero            = 2;
-let kTrapDivUnrepresentable   = 3;
-let kTrapRemByZero            = 4;
-let kTrapFloatUnrepresentable = 5;
-let kTrapFuncInvalid          = 6;
-let kTrapFuncSigMismatch      = 7;
-let kTrapInvalidIndex         = 8;
-
-let kTrapMsgs = [
-  "unreachable",
-  "memory access out of bounds",
-  "divide by zero",
-  "divide result unrepresentable",
-  "remainder by zero",
-  "integer result unrepresentable",
-  "invalid function",
-  "function signature mismatch",
-  "invalid index into function table"
-];
-
-function assertTraps(trap, code) {
-  var threwException = true;
-  try {
-    if (typeof code === 'function') {
-      code();
-    } else {
-      eval(code);
-    }
-    threwException = false;
-  } catch (e) {
-    assertEquals("object", typeof e);
-    assertEquals(kTrapMsgs[trap], e.message);
-    // Success.
-    return;
-  }
-  throw new MjsUnitAssertionError("Did not trap, expected: "
-                                  + kTrapMsgs[trap]);
-}
-
-function assertWasmThrows(value, code) {
-  assertEquals("number", typeof(value));
-  try {
-    if (typeof code === 'function') {
-      code();
-    } else {
-      eval(code);
-    }
-  } catch (e) {
-    assertEquals("number", typeof e);
-    assertEquals(value, e);
-    // Success.
-    return;
-  }
-  throw new MjsUnitAssertionError("Did not throw at all, expected: "
-                                  + value);
-}
diff --git a/third_party/blink/web_tests/fast/wasm/wasm-limits-test.html b/third_party/blink/web_tests/fast/wasm/wasm-limits-test.html
index fbf7bef..60577cc 100644
--- a/third_party/blink/web_tests/fast/wasm/wasm-limits-test.html
+++ b/third_party/blink/web_tests/fast/wasm/wasm-limits-test.html
@@ -3,14 +3,10 @@
 <head>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
-<script src="wasm-constants.js"></script>
-<script src="wasm-module-builder.js"></script>
-<script src="wasm-limits-tests-common.js"></script>
 <script src="wasm-limits-tests.js"></script>
 </head>
 <body>
   <script>
-    test(TestBuffersAreCorrect, "wasm limits buffers are setup correctly");
     test(TestSyncCompile, "wasm sync compile test");
     test(NoParameters, "wasm no/invalid parameters passthrough");
     promise_test(NoParameters_Promise, "wasm no/invalid parameters passtrhough (promise APIs)");
diff --git a/third_party/blink/web_tests/fast/wasm/wasm-limits-tests-common.js b/third_party/blink/web_tests/fast/wasm/wasm-limits-tests-common.js
deleted file mode 100644
index 51c91e8..0000000
--- a/third_party/blink/web_tests/fast/wasm/wasm-limits-tests-common.js
+++ /dev/null
@@ -1,28 +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.
-
-function createTestBuffers(limit) {
-  let builder = new WasmModuleBuilder();
-  let body = [];
-  var fct = builder.addFunction("f", kSig_v_v);
-
-  var l = builder.toBuffer().byteLength;
-
-  // in the bare bones buffer, the size of the function f
-  // is 0, which is encoded in 1 byte. For the 2^12 case,
-  // we need 2 bytes. Then, the function ends in kExprEnd,
-  // so we need that accounted, too.
-  var remaining = limit - l - 3;
-
-  for (var i = 0; i < remaining; ++i) body.push(kExprNop);
-  fct.addBody(body);
-
-  var small_buffer = builder.toBuffer();
-  // body is now 1 larger than before, because it has the kExpEnd at the end.
-  // replace that with kExprNop, and generate a new buffer.
-  body[body.length-1] = kExprNop;
-  fct.addBody(body);
-  var large_buffer = builder.toBuffer();
-  return {small: small_buffer, large: large_buffer};
-}
diff --git a/third_party/blink/web_tests/fast/wasm/wasm-limits-tests.js b/third_party/blink/web_tests/fast/wasm/wasm-limits-tests.js
index 13e7ccd7..1725685 100644
--- a/third_party/blink/web_tests/fast/wasm/wasm-limits-tests.js
+++ b/third_party/blink/web_tests/fast/wasm/wasm-limits-tests.js
@@ -2,22 +2,58 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-var limit = Math.pow(2, 12);
+const limit = Math.pow(2, 23);
+
+const kWasmH0 = 0;
+const kWasmH1 = 0x61;
+const kWasmH2 = 0x73;
+const kWasmH3 = 0x6d;
+
+const kWasmV0 = 0x1;
+const kWasmV1 = 0;
+const kWasmV2 = 0;
+const kWasmV3 = 0;
+
+function wasmSignedLeb(val, max_len = 5) {
+  if (val == null) throw new Error("Leb value many not be null/undefined");
+  let res = [];
+  for (let i = 0; i < max_len; ++i) {
+    let v = val & 0x7f;
+    // If {v} sign-extended from 7 to 32 bits is equal to val, we are done.
+    if (((v << 25) >> 25) == val) {
+      res.push(v);
+      return res;
+    }
+    res.push(v | 0x80);
+    val = val >> 7;
+  }
+  throw new Error(
+      'Leb value <' + val + '> exceeds maximum length of ' + max_len);
+}
+
+function createTestBuffer(limit) {
+  const buffer = new Uint8Array(limit);
+  const header = [
+    kWasmH0, kWasmH1, kWasmH2, kWasmH3, // magic word
+    kWasmV0, kWasmV1, kWasmV2, kWasmV3, // version
+    0                                   // custom section
+  ];
+  // We calculate the section length so that the total module size is `limit`.
+  // For that we have to calculate the length of the leb encoding of the section
+  // length.
+  const sectionLength = limit - header.length -
+      wasmSignedLeb(limit).length;
+  const lengthBytes = wasmSignedLeb(sectionLength);
+  buffer.set(header);
+  buffer.set(lengthBytes, header.length);
+  return buffer;
+}
 
 function NoParameters() {
-  function ExpectTypeError(f) {
-    try {
-      f();
-    } catch (e) {
-      assert_true(e instanceof TypeError)
-      return;
-    }
-    assert_unreached();
-  }
-  ExpectTypeError(() => new WebAssembly.Module());
-  ExpectTypeError(() => new WebAssembly.Module("a"));
-  ExpectTypeError(() => new WebAssembly.Instance());
-  ExpectTypeError(() => new WebAssembly.Instance("a"));
+  assert_throws_js(TypeError, () => new WebAssembly.Module());
+  assert_throws_js(TypeError, () => new WebAssembly.Module("a"));
+  assert_throws_js(TypeError, () => new WebAssembly.Instance());
+  assert_throws_js(TypeError, () => new WebAssembly.Instance("a"));
 }
 
 function NoParameters_Promise() {
@@ -32,36 +68,40 @@
   ]);
 }
 
-function TestBuffersAreCorrect() {
-  var buffs = createTestBuffers(limit);
-  assert_equals(buffs.small.byteLength, limit);
-  assert_equals(buffs.large.byteLength, limit + 1);
-}
-
-function compileFailsWithError(buffer, error_type) {
-  try {
-    new WebAssembly.Module(buffer);
-  } catch (e) {
-    assert_true(e instanceof error_type);
-  }
-}
-
 function TestSyncCompile() {
-  var buffs = createTestBuffers(limit);
-  assert_true(new WebAssembly.Module(buffs.small)
+  assert_true(new WebAssembly.Module(createTestBuffer(limit))
               instanceof WebAssembly.Module);
-  compileFailsWithError(buffs.large, RangeError);
+  assert_throws_js(
+      RangeError, () => new WebAssembly.Module(createTestBuffer(limit + 1)));
 }
 
 function TestPromiseCompile() {
-  return WebAssembly.compile(createTestBuffers(limit).large)
+  return WebAssembly.compile(createTestBuffer(limit + 1))
     .then(m => assert_true(m instanceof WebAssembly.Module));
 }
 
+function WorkerCode() {
+  onmessage = (event) => {
+    const buffer = event.data;
+    try {
+      let module = new WebAssembly.Module(buffer);
+      let instance = new WebAssembly.Instance(module);
+      postMessage(
+          module instanceof WebAssembly.Module &&
+          instance instanceof WebAssembly.Instance);
+    } catch (e) {
+      postMessage(false);
+    }
+  }
+}
+
 function TestWorkerCompileAndInstantiate() {
-  var worker = new Worker("wasm-limits-worker.js");
+  const workerBlob = new Blob(['(', WorkerCode.toString(), ')()']);
+  const blobUrl = blobURL = URL.createObjectURL(
+      workerBlob, {type: 'application/javascript; charset=utf-8'});
+  const worker = new Worker(blobUrl);
   return new Promise((resolve, reject) => {
-    worker.postMessage(createTestBuffers(limit).large);
+    worker.postMessage(createTestBuffer(limit + 1));
     worker.onmessage = function(event) {
       resolve(event.data);
     }
@@ -71,38 +111,34 @@
 }
 
 function TestPromiseCompileSyncInstantiate() {
-  return WebAssembly.compile(createTestBuffers(limit).large)
-    .then (m => new WebAssembly.Instance(m))
-    .then(assert_unreached,
-          e => assert_true(e instanceof RangeError));
+  return WebAssembly.compile(createTestBuffer(limit + 1))
+      .then(
+          m => assert_throws_js(RangeError, () => new WebAssembly.Instance(m)));
 }
 
 function TestPromiseCompileAsyncInstantiateFromBuffer() {
-  return WebAssembly.instantiate(createTestBuffers(limit).large)
+  return WebAssembly.instantiate(createTestBuffer(limit + 1))
     .then(i => assert_true(i.instance instanceof WebAssembly.Instance),
           assert_unreached);
 }
 
 function TestPromiseCompileAsyncInstantiateFromModule() {
-  return WebAssembly.compile(createTestBuffers(limit).large)
-    .then(m => {
-      assert_true(m instanceof WebAssembly.Module);
-      return WebAssembly.instantiate(m).
-        then(i => assert_true(i instanceof WebAssembly.Instance),
-             assert_unreached);
-    },
-    assert_unreached);
+  return WebAssembly.compile(createTestBuffer(limit + 1)).then(m => {
+    assert_true(m instanceof WebAssembly.Module);
+    return WebAssembly.instantiate(m).then(
+        i => assert_true(i instanceof WebAssembly.Instance), assert_unreached);
+  }, assert_unreached);
 }
 
 
 function TestCompileFromPromise() {
-  return Promise.resolve(createTestBuffers(limit).large)
+  return Promise.resolve(createTestBuffer(limit + 1))
     .then(WebAssembly.compile)
     .then(m => assert_true(m instanceof WebAssembly.Module))
 }
 
 function TestInstantiateFromPromise() {
-  return Promise.resolve(createTestBuffers(limit).large)
+  return Promise.resolve(createTestBuffer(limit + 1))
     .then(WebAssembly.instantiate)
     .then(pair => {
       assert_true(pair.module instanceof WebAssembly.Module);
@@ -111,7 +147,7 @@
 }
 
 function TestInstantiateFromPromiseChain() {
-  return Promise.resolve(createTestBuffers(limit).large)
+  return Promise.resolve(createTestBuffer(limit + 1))
     .then(WebAssembly.compile)
     .then(WebAssembly.instantiate)
     .then(i => assert_true(i instanceof WebAssembly.Instance))
diff --git a/third_party/blink/web_tests/fast/wasm/wasm-limits-worker.js b/third_party/blink/web_tests/fast/wasm/wasm-limits-worker.js
deleted file mode 100644
index 58df999..0000000
--- a/third_party/blink/web_tests/fast/wasm/wasm-limits-worker.js
+++ /dev/null
@@ -1,21 +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.
-
-this.importScripts("wasm-constants.js");
-this.importScripts("wasm-module-builder.js");
-this.importScripts("wasm-limits-tests-common.js");
-
-onmessage = function(limit) {
-  var buffer = createTestBuffers(limit).large;
-  var m = undefined;
-  var i = undefined;
-  try {
-    m = new WebAssembly.Module(buffer);
-    i = new WebAssembly.Instance(m);
-  } catch (e) {
-    postMessage(false);
-  }
-  postMessage(m instanceof WebAssembly.Module &&
-              i instanceof WebAssembly.Instance);
-}
diff --git a/third_party/blink/web_tests/fast/wasm/wasm-module-builder.js b/third_party/blink/web_tests/fast/wasm/wasm-module-builder.js
deleted file mode 100644
index 0d8c698..0000000
--- a/third_party/blink/web_tests/fast/wasm/wasm-module-builder.js
+++ /dev/null
@@ -1,581 +0,0 @@
-// Copyright 2017 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Used for encoding f32 and double constants to bits.
-let __buffer = new ArrayBuffer(8);
-let byte_view = new Int8Array(__buffer);
-let f32_view = new Float32Array(__buffer);
-let f64_view = new Float64Array(__buffer);
-
-class Binary extends Array {
-  emit_u8(val) {
-    this.push(val);
-  }
-
-  emit_u16(val) {
-    this.push(val & 0xff);
-    this.push((val >> 8) & 0xff);
-  }
-
-  emit_u32(val) {
-    this.push(val & 0xff);
-    this.push((val >> 8) & 0xff);
-    this.push((val >> 16) & 0xff);
-    this.push((val >> 24) & 0xff);
-  }
-
-  emit_u32v(val) {
-    while (true) {
-      let v = val & 0xff;
-      val = val >>> 7;
-      if (val == 0) {
-        this.push(v);
-        break;
-      }
-      this.push(v | 0x80);
-    }
-  }
-
-  emit_bytes(data) {
-    for (let i = 0; i < data.length; i++) {
-      this.push(data[i] & 0xff);
-    }
-  }
-
-  emit_string(string) {
-    // When testing illegal names, we pass a byte array directly.
-    if (string instanceof Array) {
-      this.emit_u32v(string.length);
-      this.emit_bytes(string);
-      return;
-    }
-
-    // This is the hacky way to convert a JavaScript string to a UTF8 encoded
-    // string only containing single-byte characters.
-    let string_utf8 = unescape(encodeURIComponent(string));
-    this.emit_u32v(string_utf8.length);
-    for (let i = 0; i < string_utf8.length; i++) {
-      this.emit_u8(string_utf8.charCodeAt(i));
-    }
-  }
-
-  emit_header() {
-    this.push(kWasmH0, kWasmH1, kWasmH2, kWasmH3,
-              kWasmV0, kWasmV1, kWasmV2, kWasmV3);
-  }
-
-  emit_section(section_code, content_generator) {
-    // Emit section name.
-    this.emit_u8(section_code);
-    // Emit the section to a temporary buffer: its full length isn't know yet.
-    let section = new Binary;
-    content_generator(section);
-    // Emit section length.
-    this.emit_u32v(section.length);
-    // Copy the temporary buffer.
-    this.push(...section);
-  }
-}
-
-class WasmFunctionBuilder {
-  constructor(module, name, type_index) {
-    this.module = module;
-    this.name = name;
-    this.type_index = type_index;
-    this.body = [];
-  }
-
-  exportAs(name) {
-    this.module.addExport(name, this.index);
-    return this;
-  }
-
-  exportFunc() {
-    this.exportAs(this.name);
-    return this;
-  }
-
-  addBody(body) {
-    for (let b of body) {
-      if (typeof b != 'number') throw new Error("invalid body");
-    }
-    this.body = body;
-    // Automatically add the end for the function block to the body.
-    body.push(kExprEnd);
-    return this;
-  }
-
-  addBodyWithEnd(body) {
-    this.body = body;
-    return this;
-  }
-
-  addLocals(locals) {
-    this.locals = locals;
-    return this;
-  }
-
-  end() {
-    return this.module;
-  }
-}
-
-class WasmGlobalBuilder {
-  constructor(module, type, mutable) {
-    this.module = module;
-    this.type = type;
-    this.mutable = mutable;
-    this.init = 0;
-  }
-
-  exportAs(name) {
-    this.module.exports.push({name: name, kind: kExternalGlobal,
-                              index: this.index});
-    return this;
-  }
-}
-
-class WasmModuleBuilder {
-  constructor() {
-    this.types = [];
-    this.imports = [];
-    this.exports = [];
-    this.globals = [];
-    this.functions = [];
-    this.function_table = [];
-    this.function_table_length = 0;
-    this.function_table_inits = [];
-    this.segments = [];
-    this.explicit = [];
-    this.num_imported_funcs = 0;
-    this.num_imported_globals = 0;
-    return this;
-  }
-
-  addStart(start_index) {
-    this.start_index = start_index;
-    return this;
-  }
-
-  addMemory(min, max, exp) {
-    this.memory = {min: min, max: max, exp: exp};
-    return this;
-  }
-
-  addExplicitSection(bytes) {
-    this.explicit.push(bytes);
-    return this;
-  }
-
-  stringToBytes(name) {
-    var result = new Binary();
-    result.emit_u32v(name.length);
-    for (var i = 0; i < name.length; i++) {
-      result.emit_u8(name.charCodeAt(i));
-    }
-    return result;
-  }
-
-  addCustomSection(name, bytes) {
-    name = this.stringToBytes(name);
-    var length = new Binary();
-    length.emit_u32v(name.length + bytes.length);
-    this.explicit.push([0, ...length, ...name, ...bytes]);
-  }
-
-  addType(type) {
-    // TODO: canonicalize types?
-    this.types.push(type);
-    return this.types.length - 1;
-  }
-
-  addGlobal(local_type, mutable) {
-    let glob = new WasmGlobalBuilder(this, local_type, mutable);
-    glob.index = this.globals.length + this.num_imported_globals;
-    this.globals.push(glob);
-    return glob;
-  }
-
-  addFunction(name, type) {
-    let type_index = (typeof type) == "number" ? type : this.addType(type);
-    let func = new WasmFunctionBuilder(this, name, type_index);
-    func.index = this.functions.length + this.num_imported_funcs;
-    this.functions.push(func);
-    return func;
-  }
-
-  addImport(module = "", name, type) {
-    let type_index = (typeof type) == "number" ? type : this.addType(type);
-    this.imports.push({module: module, name: name, kind: kExternalFunction,
-                       type: type_index});
-    return this.num_imported_funcs++;
-  }
-
-  addImportedGlobal(module = "", name, type) {
-    let o = {module: module, name: name, kind: kExternalGlobal, type: type,
-             mutable: false}
-    this.imports.push(o);
-    return this.num_imported_globals++;
-  }
-
-  addImportedMemory(module = "", name, initial = 0, maximum) {
-    let o = {module: module, name: name, kind: kExternalMemory,
-             initial: initial, maximum: maximum};
-    this.imports.push(o);
-    return this;
-  }
-
-  addImportedTable(module = "", name, initial, maximum) {
-    let o = {module: module, name: name, kind: kExternalTable, initial: initial,
-             maximum: maximum};
-    this.imports.push(o);
-  }
-
-  addExport(name, index) {
-    this.exports.push({name: name, kind: kExternalFunction, index: index});
-    return this;
-  }
-
-  addExportOfKind(name, kind, index) {
-    this.exports.push({name: name, kind: kind, index: index});
-    return this;
-  }
-
-  addDataSegment(addr, data, is_global = false) {
-    this.segments.push({addr: addr, data: data, is_global: is_global});
-    return this.segments.length - 1;
-  }
-
-  exportMemoryAs(name) {
-    this.exports.push({name: name, kind: kExternalMemory, index: 0});
-  }
-
-  addFunctionTableInit(base, is_global, array, is_import = false) {
-    this.function_table_inits.push({base: base, is_global: is_global,
-                                    array: array});
-    if (!is_global) {
-      var length = base + array.length;
-      if (length > this.function_table_length && !is_import) {
-        this.function_table_length = length;
-      }
-    }
-    return this;
-  }
-
-  appendToTable(array) {
-    return this.addFunctionTableInit(this.function_table.length, false, array);
-  }
-
-  setFunctionTableLength(length) {
-    this.function_table_length = length;
-    return this;
-  }
-
-  toArray(debug = false) {
-    let binary = new Binary;
-    let wasm = this;
-
-    // Add header
-    binary.emit_header();
-
-    // Add type section
-    if (wasm.types.length > 0) {
-      if (debug) print("emitting types @ " + binary.length);
-      binary.emit_section(kTypeSectionCode, section => {
-        section.emit_u32v(wasm.types.length);
-        for (let type of wasm.types) {
-          section.emit_u8(kWasmFunctionTypeForm);
-          section.emit_u32v(type.params.length);
-          for (let param of type.params) {
-            section.emit_u8(param);
-          }
-          section.emit_u32v(type.results.length);
-          for (let result of type.results) {
-            section.emit_u8(result);
-          }
-        }
-      });
-    }
-
-    // Add imports section
-    if (wasm.imports.length > 0) {
-      if (debug) print("emitting imports @ " + binary.length);
-      binary.emit_section(kImportSectionCode, section => {
-        section.emit_u32v(wasm.imports.length);
-        for (let imp of wasm.imports) {
-          section.emit_string(imp.module);
-          section.emit_string(imp.name || '');
-          section.emit_u8(imp.kind);
-          if (imp.kind == kExternalFunction) {
-            section.emit_u32v(imp.type);
-          } else if (imp.kind == kExternalGlobal) {
-            section.emit_u32v(imp.type);
-            section.emit_u8(imp.mutable);
-          } else if (imp.kind == kExternalMemory) {
-            var has_max = (typeof imp.maximum) != "undefined";
-            section.emit_u8(has_max ? 1 : 0); // flags
-            section.emit_u32v(imp.initial); // initial
-            if (has_max) section.emit_u32v(imp.maximum); // maximum
-          } else if (imp.kind == kExternalTable) {
-            section.emit_u8(kWasmAnyFunctionTypeForm);
-            var has_max = (typeof imp.maximum) != "undefined";
-            section.emit_u8(has_max ? 1 : 0); // flags
-            section.emit_u32v(imp.initial); // initial
-            if (has_max) section.emit_u32v(imp.maximum); // maximum
-          } else {
-            throw new Error("unknown/unsupported import kind " + imp.kind);
-          }
-        }
-      });
-    }
-
-    // Add functions declarations
-    let has_names = false;
-    let names = false;
-    if (wasm.functions.length > 0) {
-      if (debug) print("emitting function decls @ " + binary.length);
-      binary.emit_section(kFunctionSectionCode, section => {
-        section.emit_u32v(wasm.functions.length);
-        for (let func of wasm.functions) {
-          has_names = has_names || (func.name != undefined &&
-                                   func.name.length > 0);
-          section.emit_u32v(func.type_index);
-        }
-      });
-    }
-
-    // Add function_table.
-    if (wasm.function_table_length > 0) {
-      if (debug) print("emitting table @ " + binary.length);
-      binary.emit_section(kTableSectionCode, section => {
-        section.emit_u8(1);  // one table entry
-        section.emit_u8(kWasmAnyFunctionTypeForm);
-        section.emit_u8(1);
-        section.emit_u32v(wasm.function_table_length);
-        section.emit_u32v(wasm.function_table_length);
-      });
-    }
-
-    // Add memory section
-    if (wasm.memory != undefined) {
-      if (debug) print("emitting memory @ " + binary.length);
-      binary.emit_section(kMemorySectionCode, section => {
-        section.emit_u8(1);  // one memory entry
-        section.emit_u32v(kResizableMaximumFlag);
-        section.emit_u32v(wasm.memory.min);
-        section.emit_u32v(wasm.memory.max);
-      });
-    }
-
-    // Add global section.
-    if (wasm.globals.length > 0) {
-      if (debug) print ("emitting globals @ " + binary.length);
-      binary.emit_section(kGlobalSectionCode, section => {
-        section.emit_u32v(wasm.globals.length);
-        for (let global of wasm.globals) {
-          section.emit_u8(global.type);
-          section.emit_u8(global.mutable);
-          if ((typeof global.init_index) == "undefined") {
-            // Emit a constant initializer.
-            switch (global.type) {
-            case kWasmI32:
-              section.emit_u8(kExprI32Const);
-              section.emit_u32v(global.init);
-              break;
-            case kWasmI64:
-              section.emit_u8(kExprI64Const);
-              section.emit_u32v(global.init);
-              break;
-            case kWasmF32:
-              section.emit_u8(kExprF32Const);
-              f32_view[0] = global.init;
-              section.emit_u8(byte_view[0]);
-              section.emit_u8(byte_view[1]);
-              section.emit_u8(byte_view[2]);
-              section.emit_u8(byte_view[3]);
-              break;
-            case kWasmF64:
-              section.emit_u8(kExprF64Const);
-              f64_view[0] = global.init;
-              section.emit_u8(byte_view[0]);
-              section.emit_u8(byte_view[1]);
-              section.emit_u8(byte_view[2]);
-              section.emit_u8(byte_view[3]);
-              section.emit_u8(byte_view[4]);
-              section.emit_u8(byte_view[5]);
-              section.emit_u8(byte_view[6]);
-              section.emit_u8(byte_view[7]);
-              break;
-            }
-          } else {
-            // Emit a global-index initializer.
-            section.emit_u8(kExprGetGlobal);
-            section.emit_u32v(global.init_index);
-          }
-          section.emit_u8(kExprEnd);  // end of init expression
-        }
-      });
-    }
-
-    // Add export table.
-    var mem_export = (wasm.memory != undefined && wasm.memory.exp);
-    var exports_count = wasm.exports.length + (mem_export ? 1 : 0);
-    if (exports_count > 0) {
-      if (debug) print("emitting exports @ " + binary.length);
-      binary.emit_section(kExportSectionCode, section => {
-        section.emit_u32v(exports_count);
-        for (let exp of wasm.exports) {
-          section.emit_string(exp.name);
-          section.emit_u8(exp.kind);
-          section.emit_u32v(exp.index);
-        }
-        if (mem_export) {
-          section.emit_string("memory");
-          section.emit_u8(kExternalMemory);
-          section.emit_u8(0);
-        }
-      });
-    }
-
-    // Add start function section.
-    if (wasm.start_index != undefined) {
-      if (debug) print("emitting start function @ " + binary.length);
-      binary.emit_section(kStartSectionCode, section => {
-        section.emit_u32v(wasm.start_index);
-      });
-    }
-
-    // Add table elements.
-    if (wasm.function_table_inits.length > 0) {
-      if (debug) print("emitting table @ " + binary.length);
-      binary.emit_section(kElementSectionCode, section => {
-        var inits = wasm.function_table_inits;
-        section.emit_u32v(inits.length);
-        section.emit_u8(0); // table index
-
-        for (let init of inits) {
-          if (init.is_global) {
-            section.emit_u8(kExprGetGlobal);
-          } else {
-            section.emit_u8(kExprI32Const);
-          }
-          section.emit_u32v(init.base);
-          section.emit_u8(kExprEnd);
-          section.emit_u32v(init.array.length);
-          for (let index of init.array) {
-            section.emit_u32v(index);
-          }
-        }
-      });
-    }
-
-    // Add function bodies.
-    if (wasm.functions.length > 0) {
-      // emit function bodies
-      if (debug) print("emitting code @ " + binary.length);
-      binary.emit_section(kCodeSectionCode, section => {
-        section.emit_u32v(wasm.functions.length);
-        for (let func of wasm.functions) {
-          // Function body length will be patched later.
-          let local_decls = [];
-          let l = func.locals;
-          if (l != undefined) {
-            let local_decls_count = 0;
-            if (l.i32_count > 0) {
-              local_decls.push({count: l.i32_count, type: kWasmI32});
-            }
-            if (l.i64_count > 0) {
-              local_decls.push({count: l.i64_count, type: kWasmI64});
-            }
-            if (l.f32_count > 0) {
-              local_decls.push({count: l.f32_count, type: kWasmF32});
-            }
-            if (l.f64_count > 0) {
-              local_decls.push({count: l.f64_count, type: kWasmF64});
-            }
-          }
-
-          let header = new Binary;
-          header.emit_u32v(local_decls.length);
-          for (let decl of local_decls) {
-            header.emit_u32v(decl.count);
-            header.emit_u8(decl.type);
-          }
-
-          section.emit_u32v(header.length + func.body.length);
-          section.emit_bytes(header);
-          section.emit_bytes(func.body);
-        }
-      });
-    }
-
-    // Add data segments.
-    if (wasm.segments.length > 0) {
-      if (debug) print("emitting data segments @ " + binary.length);
-      binary.emit_section(kDataSectionCode, section => {
-        section.emit_u32v(wasm.segments.length);
-        for (let seg of wasm.segments) {
-          section.emit_u8(0);  // linear memory index 0
-          if (seg.is_global) {
-            // initializer is a global variable
-            section.emit_u8(kExprGetGlobal);
-            section.emit_u32v(seg.addr);
-          } else {
-            // initializer is a constant
-            section.emit_u8(kExprI32Const);
-            section.emit_u32v(seg.addr);
-          }
-          section.emit_u8(kExprEnd);
-          section.emit_u32v(seg.data.length);
-          section.emit_bytes(seg.data);
-        }
-      });
-    }
-
-    // Add any explicitly added sections
-    for (let exp of wasm.explicit) {
-      if (debug) print("emitting explicit @ " + binary.length);
-      binary.emit_bytes(exp);
-    }
-
-    // Add function names.
-    if (has_names) {
-      if (debug) print("emitting names @ " + binary.length);
-      binary.emit_section(kUnknownSectionCode, section => {
-        section.emit_string("name");
-        var count = wasm.functions.length + wasm.num_imported_funcs;
-        section.emit_u32v(count);
-        for (var i = 0; i < wasm.num_imported_funcs; i++) {
-          section.emit_u8(0); // empty string
-          section.emit_u8(0); // local names count == 0
-        }
-        for (let func of wasm.functions) {
-          var name = func.name == undefined ? "" : func.name;
-          section.emit_string(name);
-          section.emit_u8(0);  // local names count == 0
-        }
-      });
-    }
-
-    return binary;
-  }
-
-  toBuffer(debug = false) {
-    let bytes = this.toArray(debug);
-    let buffer = new ArrayBuffer(bytes.length);
-    let view = new Uint8Array(buffer);
-    for (let i = 0; i < bytes.length; i++) {
-      let val = bytes[i];
-      if ((typeof val) == "string") val = val.charCodeAt(0);
-      view[i] = val | 0;
-    }
-    return buffer;
-  }
-
-  instantiate(ffi) {
-    let module = new WebAssembly.Module(this.toBuffer());
-    let instance = new WebAssembly.Instance(module, ffi);
-    return instance;
-  }
-}
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-display-none-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-display-none-expected.txt
index fbde0ea..a5b71fe 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-display-none-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-display-none-expected.txt
@@ -38,8 +38,7 @@
     animation: animName 1000s;
         animation-duration: 1000s;
         animation-timing-function: ease;
-        animation-delay-start: 0s;
-        animation-delay-end: 0s;
+        animation-delay: 0s;
         animation-iteration-count: 1;
         animation-direction: normal;
         animation-fill-mode: none;
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-expected.txt
index 12864ac..28320f1c 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-keyframes-expected.txt
@@ -9,8 +9,7 @@
     animation: animName 1s 2s, mediaAnim 2s, doesNotExist 3s, styleSheetAnim 0s;
         animation-duration: 1s, 2s, 3s, 0s;
         animation-timing-function: ease, ease, ease, ease;
-        animation-delay-start: 2s, 0s, 0s, 0s;
-        animation-delay-end: 0s, 0s, 0s, 0s;
+        animation-delay: 2s, 0s, 0s, 0s;
         animation-iteration-count: 1, 1, 1, 1;
         animation-direction: normal, normal, normal, normal;
         animation-fill-mode: none, none, none, none;
@@ -65,8 +64,7 @@
     animation: animName 1s 2s, mediaAnim 2s, doesNotExist 3s, styleSheetAnim 0s;
         animation-duration: 1s, 2s, 3s, 0s;
         animation-timing-function: ease, ease, ease, ease;
-        animation-delay-start: 2s, 0s, 0s, 0s;
-        animation-delay-end: 0s, 0s, 0s, 0s;
+        animation-delay: 2s, 0s, 0s, 0s;
         animation-iteration-count: 1, 1, 1, 1;
         animation-direction: normal, normal, normal, normal;
         animation-fill-mode: none, none, none, none;
@@ -121,8 +119,7 @@
     animation: animName 1s 2s, mediaAnim 2s, doesNotExist 3s, styleSheetAnim 0s;
         animation-duration: 1s, 2s, 3s, 0s;
         animation-timing-function: ease, ease, ease, ease;
-        animation-delay-start: 2s, 0s, 0s, 0s;
-        animation-delay-end: 0s, 0s, 0s, 0s;
+        animation-delay: 2s, 0s, 0s, 0s;
         animation-iteration-count: 1, 1, 1, 1;
         animation-direction: normal, normal, normal, normal;
         animation-fill-mode: none, none, none, none;
@@ -177,8 +174,7 @@
     animation: animName 1s 2s, mediaAnim 2s, doesNotExist 3s, styleSheetAnim 0s;
         animation-duration: 1s, 2s, 3s, 0s;
         animation-timing-function: ease, ease, ease, ease;
-        animation-delay-start: 2s, 0s, 0s, 0s;
-        animation-delay-end: 0s, 0s, 0s, 0s;
+        animation-delay: 2s, 0s, 0s, 0s;
         animation-iteration-count: 1, 1, 1, 1;
         animation-direction: normal, normal, normal, normal;
         animation-fill-mode: none, none, none, none;
@@ -233,8 +229,7 @@
     animation: animName 1s 2s, mediaAnim 2s, doesNotExist 3s, styleSheetAnim 0s;
         animation-duration: 1s, 2s, 3s, 0s;
         animation-timing-function: ease, ease, ease, ease;
-        animation-delay-start: 2s, 0s, 0s, 0s;
-        animation-delay-end: 0s, 0s, 0s, 0s;
+        animation-delay: 2s, 0s, 0s, 0s;
         animation-iteration-count: 1, 1, 1, 1;
         animation-direction: normal, normal, normal, normal;
         animation-fill-mode: none, none, none, none;
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/cache-storage-storage-key-track-untrack.js b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/cache-storage-storage-key-track-untrack.js
index e5feaa9..2c42655 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/cache-storage-storage-key-track-untrack.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/cache-storage-storage-key-track-untrack.js
@@ -3,6 +3,9 @@
       `Tests that tracking and untracking CacheStorage for storage key works\n`);
   await dp.Page.enable();
 
+  // Remove the test cache to prevent leaking from other tests.
+  await session.evaluateAsync('caches.delete("test-cache")');
+
   const frameId = (await dp.Page.getResourceTree()).result.frameTree.frame.id;
   const storageKey = (await dp.Storage.getStorageKeyForFrame({
                        frameId: frameId
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/dom-storage-storage-key-clear.js b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/dom-storage-storage-key-clear.js
index 9fde068..02cfb98 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/dom-storage-storage-key-clear.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/dom-storage-storage-key-clear.js
@@ -2,6 +2,9 @@
   const {dp, session} = await testRunner.startBlank(
       `Tests that clearing data works for DOMStorage with storageKey\n`);
 
+  // Clear storage to prevent leaking from other tests.
+  await session.evaluate('window.localStorage.clear()')
+
   await dp.DOMStorage.enable();
   await dp.Page.enable();
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-delete-db.js b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-delete-db.js
index 4ac6ccb..b860d68 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-delete-db.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-delete-db.js
@@ -58,6 +58,10 @@
     `);
 
     testRunner.log(`${accessAfterDelete}\n`);
+
+    // Clean up
+    await dp.IndexedDB.deleteDatabase(
+        {storageBucket, databaseName: 'test-database'});
   }
 
   const { dp, session } = await testRunner.startBlank(
diff --git a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
index def84dc..ba4cd31 100644
--- a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
+++ b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
@@ -54,8 +54,7 @@
 anchor-name: none
 anchor-scroll: none
 animation-composition: replace
-animation-delay-end: 0s
-animation-delay-start: 0s
+animation-delay: 0s
 animation-direction: normal
 animation-duration: auto
 animation-fill-mode: none
@@ -313,6 +312,10 @@
 scroll-padding-inline-start: auto
 scroll-start-block: auto
 scroll-start-inline: auto
+scroll-start-target-block: none
+scroll-start-target-inline: none
+scroll-start-target-x: none
+scroll-start-target-y: none
 scroll-start-x: auto
 scroll-start-y: auto
 scroll-timeline-attachment: local
diff --git a/third_party/blink/web_tests/virtual/composite-clip-path-animation/README.md b/third_party/blink/web_tests/virtual/composite-clip-path-animation/README.md
new file mode 100644
index 0000000..9c8b3a2e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/composite-clip-path-animation/README.md
@@ -0,0 +1,23 @@
+This test suite contains ALL tests that have composited clip path animations,
+even incidentally. This suite ensures that composited clip path animations
+function properly.
+
+To facilitate this, this suite needs the impl thread to actually exist, as
+paint worklet based animations are not defined for when there is no impl thread
+task runner.
+
+As such, this suite runs with the arguments:
+
+--enable-blink-features=CompositeClipPathAnimation
+--enable-threaded-compositing
+
+Because composited clip path aniamtions do not work without threaded
+compositing, all tests in this suite must be virtual only if this feature is
+enabled-by-default. A corresponding suite for main thread composited animations
+exists to ensure main thread coverage is not lost
+
+If the Composite Clip Path animation is ever removed due to this feature
+maturing, this suite will need to be moved to the threaded compositing virtual
+suite. In that case, every directory/file currently tested for this suite
+would be need to be added, as well as being added to the list of exclusive
+directories for that suite.
diff --git a/third_party/blink/web_tests/virtual/composite-clip-path-animation/external/wpt/css/css-masking/clip-path/animations/README.txt b/third_party/blink/web_tests/virtual/composite-clip-path-animation/external/wpt/css/css-masking/clip-path/animations/README.txt
deleted file mode 100644
index f716030e1..0000000
--- a/third_party/blink/web_tests/virtual/composite-clip-path-animation/external/wpt/css/css-masking/clip-path/animations/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# This suite runs the test in external/wpt/css/composite-clip-path-animation with
-# --enable-blink-features=CompositeClipPathAnimation
diff --git a/third_party/blink/web_tests/virtual/force-renderer-accessibility-form-controls/README.md b/third_party/blink/web_tests/virtual/force-renderer-accessibility-form-controls/README.md
new file mode 100644
index 0000000..ff5013a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/force-renderer-accessibility-form-controls/README.md
@@ -0,0 +1 @@
+# This suite runs tests with --force-renderer-accessibility=form-controls.
diff --git a/third_party/blink/web_tests/virtual/main-thread-clip-path-animation/README.md b/third_party/blink/web_tests/virtual/main-thread-clip-path-animation/README.md
new file mode 100644
index 0000000..efb9e87
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/main-thread-clip-path-animation/README.md
@@ -0,0 +1,4 @@
+This test suite contains ALL tests that have compositable clip path animations,
+even incidentally. This suite ensures that *main thread* clip path animations
+do not lose coverage even if composited clip path animations are enabled by
+default.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index ca9da76..ec9bb213 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -3,15 +3,15 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS window.cached_cookieStore.onchange is null
-PASS window.cached_location.hash is undefined
-PASS window.cached_location.host is undefined
-PASS window.cached_location.hostname is undefined
-PASS window.cached_location.href is undefined
-PASS window.cached_location.origin is undefined
-PASS window.cached_location.pathname is undefined
-PASS window.cached_location.port is undefined
-PASS window.cached_location.protocol is undefined
-PASS window.cached_location.search is undefined
+PASS window.cached_location.hash is ''
+PASS window.cached_location.host is ''
+PASS window.cached_location.hostname is ''
+PASS window.cached_location.href is 'about:blank'
+PASS window.cached_location.origin is 'null'
+PASS window.cached_location.pathname is 'blank'
+PASS window.cached_location.port is ''
+PASS window.cached_location.protocol is 'about:'
+PASS window.cached_location.search is ''
 FAIL window.cached_location_ancestorOrigins.length should be 0. Was 1.
 PASS window.cached_locationbar.visible is false
 PASS window.cached_menubar.visible is false
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index 1d3f405..4a147b07 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -3,15 +3,15 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS window.cached_cookieStore.onchange is null
-PASS window.cached_location.hash is undefined
-PASS window.cached_location.host is undefined
-PASS window.cached_location.hostname is undefined
-PASS window.cached_location.href is undefined
-PASS window.cached_location.origin is undefined
-PASS window.cached_location.pathname is undefined
-PASS window.cached_location.port is undefined
-PASS window.cached_location.protocol is undefined
-PASS window.cached_location.search is undefined
+PASS window.cached_location.hash is ''
+PASS window.cached_location.host is ''
+PASS window.cached_location.hostname is ''
+PASS window.cached_location.href is 'about:blank'
+PASS window.cached_location.origin is 'null'
+PASS window.cached_location.pathname is 'blank'
+PASS window.cached_location.port is ''
+PASS window.cached_location.protocol is 'about:'
+PASS window.cached_location.search is ''
 FAIL window.cached_location_ancestorOrigins.length should be 0. Was 1.
 PASS window.cached_locationbar.visible is false
 PASS window.cached_menubar.visible is false
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index d80ef98..9c68cc8 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -3,15 +3,15 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 PASS window.cached_cookieStore.onchange is null
-PASS window.cached_location.hash is undefined
-PASS window.cached_location.host is undefined
-PASS window.cached_location.hostname is undefined
-PASS window.cached_location.href is undefined
-PASS window.cached_location.origin is undefined
-PASS window.cached_location.pathname is undefined
-PASS window.cached_location.port is undefined
-PASS window.cached_location.protocol is undefined
-PASS window.cached_location.search is undefined
+PASS window.cached_location.hash is ''
+PASS window.cached_location.host is ''
+PASS window.cached_location.hostname is ''
+PASS window.cached_location.href is 'about:blank'
+PASS window.cached_location.origin is 'null'
+PASS window.cached_location.pathname is 'blank'
+PASS window.cached_location.port is ''
+PASS window.cached_location.protocol is 'about:'
+PASS window.cached_location.search is ''
 FAIL window.cached_location_ancestorOrigins.length should be 0. Was 1.
 PASS window.cached_locationbar.visible is false
 PASS window.cached_menubar.visible is false
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index c1d9793..c27a42a 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -10,7 +10,16 @@
 PASS childWindow.innerWidth is 0
 PASS childWindow.isSecureContext is true
 PASS childWindow.length is 0
-PASS childWindow.location.href is undefined
+PASS childWindow.location.ancestorOrigins.length is 0
+PASS childWindow.location.hash is ''
+PASS childWindow.location.host is ''
+PASS childWindow.location.hostname is ''
+PASS childWindow.location.href is 'about:blank'
+PASS childWindow.location.origin is 'null'
+PASS childWindow.location.pathname is 'blank'
+PASS childWindow.location.port is ''
+PASS childWindow.location.protocol is 'about:'
+PASS childWindow.location.search is ''
 PASS childWindow.locationbar.visible is false
 PASS childWindow.menubar.visible is false
 PASS childWindow.name is ''
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index 285d32b..c965e73 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -10,7 +10,16 @@
 PASS childWindow.innerWidth is 0
 PASS childWindow.isSecureContext is true
 PASS childWindow.length is 0
-PASS childWindow.location.href is undefined
+PASS childWindow.location.ancestorOrigins.length is 0
+PASS childWindow.location.hash is ''
+PASS childWindow.location.host is ''
+PASS childWindow.location.hostname is ''
+PASS childWindow.location.href is 'about:blank'
+PASS childWindow.location.origin is 'null'
+PASS childWindow.location.pathname is 'blank'
+PASS childWindow.location.port is ''
+PASS childWindow.location.protocol is 'about:'
+PASS childWindow.location.search is ''
 PASS childWindow.locationbar.visible is false
 PASS childWindow.menubar.visible is false
 PASS childWindow.name is ''
diff --git a/third_party/blink/web_tests/virtual/view-transition-wide-gamut/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt b/third_party/blink/web_tests/virtual/view-transition-wide-gamut/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt
index 3500ad7..06724605 100644
--- a/third_party/blink/web_tests/virtual/view-transition-wide-gamut/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt
+++ b/third_party/blink/web_tests/virtual/view-transition-wide-gamut/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt
@@ -458,27 +458,19 @@
                                         value : ease
                                     }
                                     [2] : {
-                                        name : animation-delay-start
+                                        name : animation-delay
                                         value : 0s
                                     }
                                     [3] : {
-                                        name : animation-delay-end
-                                        value : 0s
-                                    }
-                                    [4] : {
                                         name : animation-iteration-count
                                         value : 1
                                     }
-                                    [5] : {
+                                    [4] : {
                                         name : animation-direction
                                         value : normal
                                     }
                                 ]
                                 shorthandEntries : [
-                                    [0] : {
-                                        name : animation-delay
-                                        value : 0s
-                                    }
                                 ]
                             }
                             supports : [
@@ -2614,27 +2606,19 @@
                                 value : ease
                             }
                             [2] : {
-                                name : animation-delay-start
+                                name : animation-delay
                                 value : 0s
                             }
                             [3] : {
-                                name : animation-delay-end
-                                value : 0s
-                            }
-                            [4] : {
                                 name : animation-iteration-count
                                 value : 1
                             }
-                            [5] : {
+                            [4] : {
                                 name : animation-direction
                                 value : normal
                             }
                         ]
                         shorthandEntries : [
-                            [0] : {
-                                name : animation-delay
-                                value : 0s
-                            }
                         ]
                     }
                     supports : [
diff --git a/third_party/blink/web_tests/virtual/view-transition/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt b/third_party/blink/web_tests/virtual/view-transition/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt
index 3500ad7..06724605 100644
--- a/third_party/blink/web_tests/virtual/view-transition/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt
+++ b/third_party/blink/web_tests/virtual/view-transition/inspector-protocol/css/css-get-styles-for-view-transition-expected.txt
@@ -458,27 +458,19 @@
                                         value : ease
                                     }
                                     [2] : {
-                                        name : animation-delay-start
+                                        name : animation-delay
                                         value : 0s
                                     }
                                     [3] : {
-                                        name : animation-delay-end
-                                        value : 0s
-                                    }
-                                    [4] : {
                                         name : animation-iteration-count
                                         value : 1
                                     }
-                                    [5] : {
+                                    [4] : {
                                         name : animation-direction
                                         value : normal
                                     }
                                 ]
                                 shorthandEntries : [
-                                    [0] : {
-                                        name : animation-delay
-                                        value : 0s
-                                    }
                                 ]
                             }
                             supports : [
@@ -2614,27 +2606,19 @@
                                 value : ease
                             }
                             [2] : {
-                                name : animation-delay-start
+                                name : animation-delay
                                 value : 0s
                             }
                             [3] : {
-                                name : animation-delay-end
-                                value : 0s
-                            }
-                            [4] : {
                                 name : animation-iteration-count
                                 value : 1
                             }
-                            [5] : {
+                            [4] : {
                                 name : animation-direction
                                 value : normal
                             }
                         ]
                         shorthandEntries : [
-                            [0] : {
-                                name : animation-delay
-                                value : 0s
-                            }
                         ]
                     }
                     supports : [
diff --git a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
index d89f2cb..9b367df 100644
--- a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
@@ -14,8 +14,6 @@
 animation
 animationComposition
 animationDelay
-animationDelayEnd
-animationDelayStart
 animationDirection
 animationDuration
 animationFillMode
@@ -392,6 +390,11 @@
 scrollStart
 scrollStartBlock
 scrollStartInline
+scrollStartTarget
+scrollStartTargetBlock
+scrollStartTargetInline
+scrollStartTargetX
+scrollStartTargetY
 scrollStartX
 scrollStartY
 scrollTimeline
diff --git a/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
index d3b4b8a8..3ead652 100644
--- a/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
@@ -61,8 +61,7 @@
     anchor-name
     anchor-scroll
     animation-composition
-    animation-delay-end
-    animation-delay-start
+    animation-delay
     animation-direction
     animation-duration
     animation-fill-mode
@@ -345,6 +344,10 @@
     scroll-snap-type
     scroll-start-block
     scroll-start-inline
+    scroll-start-target-block
+    scroll-start-target-inline
+    scroll-start-target-x
+    scroll-start-target-y
     scroll-start-x
     scroll-start-y
     scroll-timeline-attachment
@@ -462,8 +465,7 @@
         -webkit-text-stroke-color
         -webkit-text-stroke-width
     animation
-        animation-delay-end
-        animation-delay-start
+        animation-delay
         animation-direction
         animation-duration
         animation-fill-mode
@@ -474,9 +476,6 @@
         animation-range-start
         animation-timeline
         animation-timing-function
-    animation-delay
-        animation-delay-end
-        animation-delay-start
     animation-range
         animation-range-end
         animation-range-start
@@ -786,6 +785,9 @@
     scroll-start
         scroll-start-block
         scroll-start-inline
+    scroll-start-target
+        scroll-start-target-block
+        scroll-start-target-inline
     scroll-timeline
         scroll-timeline-attachment
         scroll-timeline-axis
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 429943c4..5e5f0c4 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -7166,6 +7166,11 @@
     getter time
     method constructor
     method toJSON
+interface PrivateAttribution
+    attribute @@toStringTag
+    method constructor
+    method getEncryptedMatchKey
+    method getHelperNetworks
 interface ProcessingInstruction : CharacterData
     attribute @@toStringTag
     getter sheet
@@ -12367,6 +12372,7 @@
     getter parent
     getter performance
     getter personalbar
+    getter privateAttribution
     getter scheduler
     getter screen
     getter screenLeft
@@ -12586,6 +12592,7 @@
     setter parent
     setter performance
     setter personalbar
+    setter privateAttribution
     setter scheduler
     setter screen
     setter screenLeft
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular-clamped.html b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular-clamped.html
new file mode 100644
index 0000000..c409b0b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular-clamped.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-framework-version.js"></script>
+<body>
+<div ng-version="300.4000.333.12.x"></div>
+<script>
+  test_framework_version("Angular",`${300 & 0xff}.${4000 & 0xff}`);
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular-clamped.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular-clamped.html.ini
new file mode 100644
index 0000000..48518853
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular-clamped.html.ini
@@ -0,0 +1,4 @@
+[detect-angular-clamped.html]
+  [Test framework version for Angular (default)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular.html b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular.html
new file mode 100644
index 0000000..38a96773
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-framework-version.js"></script>
+<body>
+<div ng-version="14.0.4"></div>
+<script>
+  test_framework_version("Angular", "14.0");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular.html.ini
new file mode 100644
index 0000000..042ac5d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-angular.html.ini
@@ -0,0 +1,4 @@
+[detect-angular.html]
+  [Test framework version for Angular (default)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-nextjs.html b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-nextjs.html
new file mode 100644
index 0000000..5302f12
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-nextjs.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-framework-version.js"></script>
+<body>
+<div id="__next"></div>
+<script>
+  window.__NEXT_DATA__ = {};
+  window.next = {version: '13.3.2-canary.11'};
+  test_framework_version("NextJS", '13.3');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-nextjs.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-nextjs.html.ini
new file mode 100644
index 0000000..00ad837
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-nextjs.html.ini
@@ -0,0 +1,4 @@
+[detect-nextjs.html]
+  [Test framework version for NextJS (default)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue2.html b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue2.html
new file mode 100644
index 0000000..efc9e29
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-framework-version.js"></script>
+<body>
+<script>
+  window.Vue = {version: "2.1.2" };
+  test_framework_version("Vue", "2.1");
+</script>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue2.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue2.html.ini
new file mode 100644
index 0000000..5e2eec3
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue2.html.ini
@@ -0,0 +1,4 @@
+[detect-vue2.html]
+  [Test framework version for Vue (default)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue3.html b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue3.html
new file mode 100644
index 0000000..d1f6be1
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue3.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-framework-version.js"></script>
+<body>
+<script>
+  window.__VUE__ = true;
+  test_framework_version("Vue", "3.0");
+</script>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue3.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue3.html.ini
new file mode 100644
index 0000000..7e44542
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/detect-vue3.html.ini
@@ -0,0 +1,4 @@
+[detect-vue3.html]
+  [Test framework version for Vue (default)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected-bad-values.html b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected-bad-values.html
new file mode 100644
index 0000000..8b5bc41
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected-bad-values.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+</script>
+<body>
+  <div id="__next"></div>
+  <script>
+
+// Version should be a string
+window.Vue = {version: 3 };
+window.__NEXT_DATA__ = {};
+
+// next without "version" key
+window.next = {ver_sion: '13.3.2-canary.11'};
+
+promise_test(async t => {
+  const recorder = internals.initializeUKMRecorder();
+  await new Promise(resolve => window.addEventListener("load", resolve));
+  await new Promise(resolve => t.step_timeout(resolve));
+  const entries = recorder.getMetrics(
+    "JavascriptFrameworkVersions",
+    ["NextJSVersion", "AngularVersion", "NuxtVersion", "VueVersion"]);
+  assert_equals(entries.length, 0);
+}, "With semi-invalid indicators, no framework should be detected");
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected-bad-values.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected-bad-values.html.ini
new file mode 100644
index 0000000..557849a1
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected-bad-values.html.ini
@@ -0,0 +1,4 @@
+[not-detected-bad-values.html]
+  [With semi-invalid indicators, no framework should be detected]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected.html b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected.html
new file mode 100644
index 0000000..a8ce5b8
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+</script>
+<body>
+  <script>
+promise_test(async t => {
+  const recorder = internals.initializeUKMRecorder();
+  await new Promise(resolve => window.addEventListener("load", resolve));
+  await new Promise(resolve => t.step_timeout(resolve));
+  const entries = recorder.getMetrics(
+    "JavascriptFrameworkVersions",
+    ["NextJSVersion", "AngularVersion", "NuxtVersion", "VueVersion"]);
+  assert_equals(entries.length, 0);
+}, "By default, no framework should be detected");
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected.html.ini b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected.html.ini
new file mode 100644
index 0000000..291097f
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/not-detected.html.ini
@@ -0,0 +1,4 @@
+[not-detected.html]
+  [By default, no framework should be detected]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/framework-detection/resources/test-framework-version.js b/third_party/blink/web_tests/wpt_internal/framework-detection/resources/test-framework-version.js
new file mode 100644
index 0000000..eb32460
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/framework-detection/resources/test-framework-version.js
@@ -0,0 +1,16 @@
+
+async function test_framework_version(framework, expected_decimal, variant = "default") {
+  promise_test(async t => {
+    const [major, minor] = expected_decimal.split('.').map(n => +n);
+    const framework_version = `${framework}Version`;
+    const recorder = internals.initializeUKMRecorder();
+    await new Promise(resolve => window.addEventListener("load", resolve));
+    await new Promise(resolve => t.step_timeout(resolve));
+    const entries = recorder.getMetrics(
+      "Blink.JavaScriptFramework.Versions", [framework_version]);
+    console.log(JSON.stringify(entries));
+    assert_equals(entries.length, 1);
+    const metrics = entries[0];
+    assert_equals(metrics[framework_version], (major << 8) | minor);
+  }, `Test framework version for ${framework} (${variant})`);
+}
diff --git a/third_party/blink/web_tests/wpt_internal/private_attribution/window_property.https.window.js b/third_party/blink/web_tests/wpt_internal/private_attribution/window_property.https.window.js
new file mode 100644
index 0000000..d9b5788
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/private_attribution/window_property.https.window.js
@@ -0,0 +1,7 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+test(() => {
+    assert_class_string(window.privateAttribution, 'PrivateAttribution');
+});
diff --git a/third_party/closure_compiler/externs/input_method_private.js b/third_party/closure_compiler/externs/input_method_private.js
index a009156..d1a8ee1 100644
--- a/third_party/closure_compiler/externs/input_method_private.js
+++ b/third_party/closure_compiler/externs/input_method_private.js
@@ -108,7 +108,6 @@
  *   spellCheck: boolean,
  *   shouldDoLearning: boolean,
  *   focusReason: !chrome.inputMethodPrivate.FocusReason,
- *   hasBeenPassword: boolean,
  *   appKey: (string|undefined)
  * }}
  */
diff --git a/third_party/eigen3/README.chromium b/third_party/eigen3/README.chromium
index a0571be..4634a14 100644
--- a/third_party/eigen3/README.chromium
+++ b/third_party/eigen3/README.chromium
@@ -1,8 +1,8 @@
 Name: Eigen
 Short Name: eigen3
 URL: http://eigen.tuxfamily.org/
-Version: 0b51f763cbbd0ed08168f88972724329f0375498
-Date: 2023/05/01
+Version: 2709f4c8fbbe71a5383de1ba27e9833f218a642d
+Date: 2023/05/11
 License: MPL 2
 License File: LICENSE
 Security Critical: Yes
diff --git a/third_party/ipcz/src/ipcz/node_connector.cc b/third_party/ipcz/src/ipcz/node_connector.cc
index f30d239..012dafd 100644
--- a/third_party/ipcz/src/ipcz/node_connector.cc
+++ b/third_party/ipcz/src/ipcz/node_connector.cc
@@ -31,6 +31,7 @@
  public:
   NodeConnectorForBrokerToNonBroker(Ref<Node> node,
                                     Ref<DriverTransport> transport,
+                                    DriverMemoryWithMapping memory,
                                     IpczConnectNodeFlags flags,
                                     std::vector<Ref<Portal>> waiting_portals,
                                     ConnectCallback callback)
@@ -39,9 +40,8 @@
                       flags,
                       std::move(waiting_portals),
                       std::move(callback)),
-        link_memory_allocation_(
-            NodeLinkMemory::AllocateMemory(node_->driver())) {
-    ABSL_ASSERT(link_memory_allocation_.mapping.is_valid());
+        link_memory_allocation_(std::move(memory)) {
+    ABSL_HARDENING_ASSERT(link_memory_allocation_.mapping.is_valid());
   }
 
   ~NodeConnectorForBrokerToNonBroker() override = default;
@@ -291,7 +291,9 @@
                                  uint64_t referral_id,
                                  uint32_t num_initial_portals,
                                  Ref<NodeLink> referrer,
-                                 Ref<DriverTransport> transport)
+                                 Ref<DriverTransport> transport,
+                                 DriverMemoryWithMapping link_memory,
+                                 DriverMemoryWithMapping client_link_memory)
       : NodeConnector(std::move(node),
                       std::move(transport),
                       IPCZ_NO_FLAGS,
@@ -299,9 +301,11 @@
                       /*callback=*/nullptr),
         referral_id_(referral_id),
         num_initial_portals_(num_initial_portals),
-        referrer_(std::move(referrer)) {
-    ABSL_ASSERT(link_memory_.mapping.is_valid());
-    ABSL_ASSERT(client_link_memory_.mapping.is_valid());
+        referrer_(std::move(referrer)),
+        link_memory_(std::move(link_memory)),
+        client_link_memory_(std::move(client_link_memory)) {
+    ABSL_HARDENING_ASSERT(link_memory_.mapping.is_valid());
+    ABSL_HARDENING_ASSERT(client_link_memory_.mapping.is_valid());
   }
 
   ~NodeConnectorForBrokerReferral() override = default;
@@ -392,16 +396,15 @@
   const Ref<NodeLink> referrer_;
   const NodeName broker_name_{node_->GetAssignedName()};
   const NodeName referred_node_name_{node_->GenerateRandomName()};
-  DriverMemoryWithMapping link_memory_{
-      NodeLinkMemory::AllocateMemory(node_->driver())};
-  DriverMemoryWithMapping client_link_memory_{
-      NodeLinkMemory::AllocateMemory(node_->driver())};
+  DriverMemoryWithMapping link_memory_;
+  DriverMemoryWithMapping client_link_memory_;
 };
 
 class NodeConnectorForBrokerToBroker : public NodeConnector {
  public:
   NodeConnectorForBrokerToBroker(Ref<Node> node,
                                  Ref<DriverTransport> transport,
+                                 DriverMemoryWithMapping memory,
                                  IpczConnectNodeFlags flags,
                                  std::vector<Ref<Portal>> waiting_portals,
                                  ConnectCallback callback)
@@ -410,9 +413,8 @@
                       flags,
                       std::move(waiting_portals),
                       std::move(callback)),
-        link_memory_allocation_(
-            NodeLinkMemory::AllocateMemory(node_->driver())) {
-    ABSL_ASSERT(link_memory_allocation_.mapping.is_valid());
+        link_memory_allocation_(std::move(memory)) {
+    ABSL_HARDENING_ASSERT(link_memory_allocation_.mapping.is_valid());
   }
 
   ~NodeConnectorForBrokerToBroker() override = default;
@@ -480,16 +482,22 @@
   const bool share_broker = (flags & IPCZ_CONNECT_NODE_SHARE_BROKER) != 0;
   const bool inherit_broker = (flags & IPCZ_CONNECT_NODE_INHERIT_BROKER) != 0;
   if (from_broker) {
+    DriverMemoryWithMapping memory =
+        NodeLinkMemory::AllocateMemory(node->driver());
+    if (!memory.mapping.is_valid()) {
+      return {nullptr, IPCZ_RESULT_RESOURCE_EXHAUSTED};
+    }
+
     if (to_broker) {
       return {MakeRefCounted<NodeConnectorForBrokerToBroker>(
-                  std::move(node), std::move(transport), flags, initial_portals,
-                  std::move(callback)),
+                  std::move(node), std::move(transport), std::move(memory),
+                  flags, initial_portals, std::move(callback)),
               IPCZ_RESULT_OK};
     }
 
     return {MakeRefCounted<NodeConnectorForBrokerToNonBroker>(
-                std::move(node), std::move(transport), flags, initial_portals,
-                std::move(callback)),
+                std::move(node), std::move(transport), std::move(memory), flags,
+                initial_portals, std::move(callback)),
             IPCZ_RESULT_OK};
   }
 
@@ -565,11 +573,14 @@
     uint64_t referral_id,
     uint32_t num_initial_portals,
     Ref<NodeLink> referrer,
-    Ref<DriverTransport> transport_to_referred_node) {
+    Ref<DriverTransport> transport_to_referred_node,
+    DriverMemoryWithMapping link_memory,
+    DriverMemoryWithMapping client_link_memory) {
   ABSL_ASSERT(node->type() == Node::Type::kBroker);
   auto connector = MakeRefCounted<NodeConnectorForBrokerReferral>(
       std::move(node), referral_id, num_initial_portals, std::move(referrer),
-      std::move(transport_to_referred_node));
+      std::move(transport_to_referred_node), std::move(link_memory),
+      std::move(client_link_memory));
 
   // The connector effectively owns itself and lives only until its transport is
   // disconnected or it receives a greeting from the referred node.
diff --git a/third_party/ipcz/src/ipcz/node_connector.h b/third_party/ipcz/src/ipcz/node_connector.h
index 9d1d9e0d..4e9beb9 100644
--- a/third_party/ipcz/src/ipcz/node_connector.h
+++ b/third_party/ipcz/src/ipcz/node_connector.h
@@ -51,13 +51,17 @@
   // non-broker node referral from `referrer`, referring a new non-broker node
   // on the remote end of `transport_to_referred_node`. This performs a
   // handshake with the referred node before introducing it and the referrer to
-  // each other.
+  // each other. `link_memory` and `client_link_memory` must be valid and will
+  // be passed respectively to the referred node (for its link to the broker)
+  // and the referring node (for its link to the referred node).
   static bool HandleNonBrokerReferral(
       Ref<Node> node,
       uint64_t referral_id,
       uint32_t num_initial_portals,
       Ref<NodeLink> referrer,
-      Ref<DriverTransport> transport_to_referred_node);
+      Ref<DriverTransport> transport_to_referred_node,
+      DriverMemoryWithMapping link_memory,
+      DriverMemoryWithMapping client_link_memory);
 
   virtual bool Connect() = 0;
 
diff --git a/third_party/ipcz/src/ipcz/node_link.cc b/third_party/ipcz/src/ipcz/node_link.cc
index a1ede6f..4d451b8 100644
--- a/third_party/ipcz/src/ipcz/node_link.cc
+++ b/third_party/ipcz/src/ipcz/node_link.cc
@@ -374,10 +374,25 @@
     return false;
   }
 
+  DriverMemoryWithMapping link_memory =
+      NodeLinkMemory::AllocateMemory(node()->driver());
+  DriverMemoryWithMapping client_link_memory =
+      NodeLinkMemory::AllocateMemory(node()->driver());
+  if (!link_memory.mapping.is_valid() ||
+      !client_link_memory.mapping.is_valid()) {
+    // Not a validation failure, but we can't accept the referral because we
+    // can't allocate link memory for one side or the other.
+    msg::NonBrokerReferralRejected rejected;
+    rejected.params().referral_id = refer.params().referral_id;
+    Transmit(rejected);
+    return true;
+  }
+
   return NodeConnector::HandleNonBrokerReferral(
       node(), refer.params().referral_id, refer.params().num_initial_portals,
       WrapRefCounted(this),
-      MakeRefCounted<DriverTransport>(std::move(transport)));
+      MakeRefCounted<DriverTransport>(std::move(transport)),
+      std::move(link_memory), std::move(client_link_memory));
 }
 
 bool NodeLink::OnNonBrokerReferralAccepted(
diff --git a/third_party/jacoco/3pp/patches/0001-hardcode-properties.patch b/third_party/jacoco/3pp/patches/0001-hardcode-properties.patch
index 4a2fa32..f6bc839 100644
--- a/third_party/jacoco/3pp/patches/0001-hardcode-properties.patch
+++ b/third_party/jacoco/3pp/patches/0001-hardcode-properties.patch
@@ -1,9 +1,37 @@
+diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java
+index fb5cf5fb..31c9889e 100644
+--- a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java
++++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java
+@@ -168,13 +168,16 @@ public class Agent implements IAgent {
+ 
+ 	private String createSessionId() {
+ 		String host;
+-		try {
+-			host = InetAddress.getLocalHost().getHostName();
+-		} catch (final Exception e) {
+-			// Also catch platform specific exceptions (like on Android) to
+-			// avoid bailing out here
+-			host = "unknownhost";
+-		}
++		// Local modification: Do not try to get host address for StrictMode.
++		// See crbug.com/1401476.
++		// try {
++		//	host = InetAddress.getLocalHost().getHostName();
++		// } catch (final Exception e) {
++		// 	// Also catch platform specific exceptions (like on Android) to
++		// 	// avoid bailing out here
++		// 	host = "unknownhost";
++		//}
++		host = "unknownhost";
+ 		return host + "-" + AbstractRuntime.createRandomId();
+ 	}
+ 
 diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/ConfigLoader.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/ConfigLoader.java
-index 9020c2fc..7c3d4399 100644
+index 9020c2fc..35fc18df 100644
 --- a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/ConfigLoader.java
 +++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/ConfigLoader.java
 @@ -33,7 +33,10 @@ final class ConfigLoader {
-
+ 
  	static Properties load(final String resource, final Properties system) {
  		final Properties result = new Properties();
 -		loadResource(resource, result);
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium
index 529ad384..8c2b086 100644
--- a/third_party/libaom/README.chromium
+++ b/third_party/libaom/README.chromium
@@ -2,8 +2,8 @@
 Short Name: libaom
 URL: https://aomedia.googlesource.com/aom/
 Version: 0
-Date: Friday May 05 2023
-Revision: 6009df0c9db34ec4776e1b49288bbdf8fe0e3e5c
+Date: Wednesday May 10 2023
+Revision: f15d8bacc72479979182f7484e0b80cf988ffd67
 CPEPrefix: cpe:/a:aomedia:aomedia:3.6.0
 License: BSD
 License File: source/libaom/LICENSE
diff --git a/third_party/libaom/source/config/config/aom_version.h b/third_party/libaom/source/config/config/aom_version.h
index 73993ec0..35f7559 100644
--- a/third_party/libaom/source/config/config/aom_version.h
+++ b/third_party/libaom/source/config/config/aom_version.h
@@ -11,9 +11,9 @@
 
 #define VERSION_MAJOR 3
 #define VERSION_MINOR 6
-#define VERSION_PATCH 0
-#define VERSION_EXTRA "590-g6009df0c9"
+#define VERSION_PATCH 1
+#define VERSION_EXTRA "600-gf15d8bacc"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "3.6.0-590-g6009df0c9"
-#define VERSION_STRING " 3.6.0-590-g6009df0c9"
+#define VERSION_STRING_NOSP "3.6.1-600-gf15d8bacc"
+#define VERSION_STRING " 3.6.1-600-gf15d8bacc"
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h
index 1db5fff..d75583d 100644
--- a/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h
@@ -2187,7 +2187,8 @@
 #define aom_sum_squares_2d_i16 aom_sum_squares_2d_i16_neon
 
 uint64_t aom_sum_squares_i16_c(const int16_t *src, uint32_t N);
-#define aom_sum_squares_i16 aom_sum_squares_i16_c
+uint64_t aom_sum_squares_i16_neon(const int16_t* src, uint32_t N);
+#define aom_sum_squares_i16 aom_sum_squares_i16_neon
 
 uint64_t aom_sum_sse_2d_i16_c(const int16_t *src, int src_stride, int width, int height, int *sum);
 uint64_t aom_sum_sse_2d_i16_neon(const int16_t *src, int src_stride, int width, int height, int *sum);
@@ -2315,10 +2316,18 @@
 #define aom_v_predictor_8x8 aom_v_predictor_8x8_neon
 
 uint64_t aom_var_2d_u16_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u16 aom_var_2d_u16_c
+uint64_t aom_var_2d_u16_neon(uint8_t* src,
+                             int src_stride,
+                             int width,
+                             int height);
+#define aom_var_2d_u16 aom_var_2d_u16_neon
 
 uint64_t aom_var_2d_u8_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u8 aom_var_2d_u8_c
+uint64_t aom_var_2d_u8_neon(uint8_t* src,
+                            int src_stride,
+                            int width,
+                            int height);
+#define aom_var_2d_u8 aom_var_2d_u8_neon
 
 unsigned int aom_variance128x128_c(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
 unsigned int aom_variance128x128_neon(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
diff --git a/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h
index 1db5fff..d75583d 100644
--- a/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h
@@ -2187,7 +2187,8 @@
 #define aom_sum_squares_2d_i16 aom_sum_squares_2d_i16_neon
 
 uint64_t aom_sum_squares_i16_c(const int16_t *src, uint32_t N);
-#define aom_sum_squares_i16 aom_sum_squares_i16_c
+uint64_t aom_sum_squares_i16_neon(const int16_t* src, uint32_t N);
+#define aom_sum_squares_i16 aom_sum_squares_i16_neon
 
 uint64_t aom_sum_sse_2d_i16_c(const int16_t *src, int src_stride, int width, int height, int *sum);
 uint64_t aom_sum_sse_2d_i16_neon(const int16_t *src, int src_stride, int width, int height, int *sum);
@@ -2315,10 +2316,18 @@
 #define aom_v_predictor_8x8 aom_v_predictor_8x8_neon
 
 uint64_t aom_var_2d_u16_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u16 aom_var_2d_u16_c
+uint64_t aom_var_2d_u16_neon(uint8_t* src,
+                             int src_stride,
+                             int width,
+                             int height);
+#define aom_var_2d_u16 aom_var_2d_u16_neon
 
 uint64_t aom_var_2d_u8_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u8 aom_var_2d_u8_c
+uint64_t aom_var_2d_u8_neon(uint8_t* src,
+                            int src_stride,
+                            int width,
+                            int height);
+#define aom_var_2d_u8 aom_var_2d_u8_neon
 
 unsigned int aom_variance128x128_c(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
 unsigned int aom_variance128x128_neon(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h
index 5d29310..9aab83bb 100644
--- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h
@@ -2596,7 +2596,8 @@
 RTCD_EXTERN uint64_t (*aom_sum_squares_2d_i16)(const int16_t *src, int stride, int width, int height);
 
 uint64_t aom_sum_squares_i16_c(const int16_t *src, uint32_t N);
-#define aom_sum_squares_i16 aom_sum_squares_i16_c
+uint64_t aom_sum_squares_i16_neon(const int16_t* src, uint32_t N);
+RTCD_EXTERN uint64_t (*aom_sum_squares_i16)(const int16_t* src, uint32_t N);
 
 uint64_t aom_sum_sse_2d_i16_c(const int16_t *src, int src_stride, int width, int height, int *sum);
 uint64_t aom_sum_sse_2d_i16_neon(const int16_t *src, int src_stride, int width, int height, int *sum);
@@ -2769,10 +2770,24 @@
 RTCD_EXTERN void (*aom_v_predictor_8x8)(uint8_t *dst, ptrdiff_t y_stride, const uint8_t *above, const uint8_t *left);
 
 uint64_t aom_var_2d_u16_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u16 aom_var_2d_u16_c
+uint64_t aom_var_2d_u16_neon(uint8_t* src,
+                             int src_stride,
+                             int width,
+                             int height);
+RTCD_EXTERN uint64_t (*aom_var_2d_u16)(uint8_t* src,
+                                       int src_stride,
+                                       int width,
+                                       int height);
 
 uint64_t aom_var_2d_u8_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u8 aom_var_2d_u8_c
+uint64_t aom_var_2d_u8_neon(uint8_t* src,
+                            int src_stride,
+                            int width,
+                            int height);
+RTCD_EXTERN uint64_t (*aom_var_2d_u8)(uint8_t* src,
+                                      int src_stride,
+                                      int width,
+                                      int height);
 
 unsigned int aom_variance128x128_c(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
 unsigned int aom_variance128x128_neon(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
@@ -3810,6 +3825,10 @@
     if (flags & HAS_NEON) aom_subtract_block = aom_subtract_block_neon;
     aom_sum_squares_2d_i16 = aom_sum_squares_2d_i16_c;
     if (flags & HAS_NEON) aom_sum_squares_2d_i16 = aom_sum_squares_2d_i16_neon;
+    aom_sum_squares_i16 = aom_sum_squares_i16_c;
+    if (flags & HAS_NEON) {
+      aom_sum_squares_i16 = aom_sum_squares_i16_neon;
+    }
     aom_sum_sse_2d_i16 = aom_sum_sse_2d_i16_c;
     if (flags & HAS_NEON) aom_sum_sse_2d_i16 = aom_sum_sse_2d_i16_neon;
     aom_v_predictor_16x16 = aom_v_predictor_16x16_c;
@@ -3880,6 +3899,14 @@
     }
     aom_v_predictor_8x8 = aom_v_predictor_8x8_c;
     if (flags & HAS_NEON) aom_v_predictor_8x8 = aom_v_predictor_8x8_neon;
+    aom_var_2d_u16 = aom_var_2d_u16_c;
+    if (flags & HAS_NEON) {
+      aom_var_2d_u16 = aom_var_2d_u16_neon;
+    }
+    aom_var_2d_u8 = aom_var_2d_u8_c;
+    if (flags & HAS_NEON) {
+      aom_var_2d_u8 = aom_var_2d_u8_neon;
+    }
     aom_variance128x128 = aom_variance128x128_c;
     if (flags & HAS_NEON) aom_variance128x128 = aom_variance128x128_neon;
     aom_variance128x64 = aom_variance128x64_c;
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h
index 1db5fff..d75583d 100644
--- a/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h
@@ -2187,7 +2187,8 @@
 #define aom_sum_squares_2d_i16 aom_sum_squares_2d_i16_neon
 
 uint64_t aom_sum_squares_i16_c(const int16_t *src, uint32_t N);
-#define aom_sum_squares_i16 aom_sum_squares_i16_c
+uint64_t aom_sum_squares_i16_neon(const int16_t* src, uint32_t N);
+#define aom_sum_squares_i16 aom_sum_squares_i16_neon
 
 uint64_t aom_sum_sse_2d_i16_c(const int16_t *src, int src_stride, int width, int height, int *sum);
 uint64_t aom_sum_sse_2d_i16_neon(const int16_t *src, int src_stride, int width, int height, int *sum);
@@ -2315,10 +2316,18 @@
 #define aom_v_predictor_8x8 aom_v_predictor_8x8_neon
 
 uint64_t aom_var_2d_u16_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u16 aom_var_2d_u16_c
+uint64_t aom_var_2d_u16_neon(uint8_t* src,
+                             int src_stride,
+                             int width,
+                             int height);
+#define aom_var_2d_u16 aom_var_2d_u16_neon
 
 uint64_t aom_var_2d_u8_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u8 aom_var_2d_u8_c
+uint64_t aom_var_2d_u8_neon(uint8_t* src,
+                            int src_stride,
+                            int width,
+                            int height);
+#define aom_var_2d_u8 aom_var_2d_u8_neon
 
 unsigned int aom_variance128x128_c(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
 unsigned int aom_variance128x128_neon(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
diff --git a/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h
index 1db5fff..d75583d 100644
--- a/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h
@@ -2187,7 +2187,8 @@
 #define aom_sum_squares_2d_i16 aom_sum_squares_2d_i16_neon
 
 uint64_t aom_sum_squares_i16_c(const int16_t *src, uint32_t N);
-#define aom_sum_squares_i16 aom_sum_squares_i16_c
+uint64_t aom_sum_squares_i16_neon(const int16_t* src, uint32_t N);
+#define aom_sum_squares_i16 aom_sum_squares_i16_neon
 
 uint64_t aom_sum_sse_2d_i16_c(const int16_t *src, int src_stride, int width, int height, int *sum);
 uint64_t aom_sum_sse_2d_i16_neon(const int16_t *src, int src_stride, int width, int height, int *sum);
@@ -2315,10 +2316,18 @@
 #define aom_v_predictor_8x8 aom_v_predictor_8x8_neon
 
 uint64_t aom_var_2d_u16_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u16 aom_var_2d_u16_c
+uint64_t aom_var_2d_u16_neon(uint8_t* src,
+                             int src_stride,
+                             int width,
+                             int height);
+#define aom_var_2d_u16 aom_var_2d_u16_neon
 
 uint64_t aom_var_2d_u8_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u8 aom_var_2d_u8_c
+uint64_t aom_var_2d_u8_neon(uint8_t* src,
+                            int src_stride,
+                            int width,
+                            int height);
+#define aom_var_2d_u8 aom_var_2d_u8_neon
 
 unsigned int aom_variance128x128_c(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
 unsigned int aom_variance128x128_neon(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
diff --git a/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h
index 1db5fff..d75583d 100644
--- a/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h
@@ -2187,7 +2187,8 @@
 #define aom_sum_squares_2d_i16 aom_sum_squares_2d_i16_neon
 
 uint64_t aom_sum_squares_i16_c(const int16_t *src, uint32_t N);
-#define aom_sum_squares_i16 aom_sum_squares_i16_c
+uint64_t aom_sum_squares_i16_neon(const int16_t* src, uint32_t N);
+#define aom_sum_squares_i16 aom_sum_squares_i16_neon
 
 uint64_t aom_sum_sse_2d_i16_c(const int16_t *src, int src_stride, int width, int height, int *sum);
 uint64_t aom_sum_sse_2d_i16_neon(const int16_t *src, int src_stride, int width, int height, int *sum);
@@ -2315,10 +2316,18 @@
 #define aom_v_predictor_8x8 aom_v_predictor_8x8_neon
 
 uint64_t aom_var_2d_u16_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u16 aom_var_2d_u16_c
+uint64_t aom_var_2d_u16_neon(uint8_t* src,
+                             int src_stride,
+                             int width,
+                             int height);
+#define aom_var_2d_u16 aom_var_2d_u16_neon
 
 uint64_t aom_var_2d_u8_c(uint8_t *src, int src_stride, int width, int height);
-#define aom_var_2d_u8 aom_var_2d_u8_c
+uint64_t aom_var_2d_u8_neon(uint8_t* src,
+                            int src_stride,
+                            int width,
+                            int height);
+#define aom_var_2d_u8 aom_var_2d_u8_neon
 
 unsigned int aom_variance128x128_c(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
 unsigned int aom_variance128x128_neon(const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int ref_stride, unsigned int *sse);
diff --git a/third_party/lottie/lottie_worker.js b/third_party/lottie/lottie_worker.js
index cb90805..ffe18ab 100644
--- a/third_party/lottie/lottie_worker.js
+++ b/third_party/lottie/lottie_worker.js
@@ -7,7 +7,7 @@
   const initialDefaultFrame = -999999;
 
   const subframeEnabled = true;
-  let expressionsPlugin;
+  let expressionsPlugin = undefined;
   const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
   const cachedColors = {};
   const bm_rounder = Math.round;
@@ -123,16 +123,11 @@
     let r;
     let g;
     let b;
-    let i;
-    let f;
-    let p;
-    let q;
-    let t;
-    i = Math.floor(h * 6);
-    f = h * 6 - i;
-    p = v * (1 - s);
-    q = v * (1 - f * s);
-    t = v * (1 - (1 - f) * s);
+    const i = Math.floor(h * 6);
+    const f = h * 6 - i;
+    const p = v * (1 - s);
+    const q = v * (1 - f * s);
+    const t = v * (1 - (1 - f) * s);
     switch (i % 6) {
       case 0:
         r = v;
@@ -898,16 +893,15 @@
               };
             }
 
-            // If called as a method of Math (Math.seedrandom()), mutate
-            // Math.random because that is how seedrandom.js has worked since v1.0.
             if (is_math_call) {
+              // If called as a method of Math (Math.seedrandom()), mutate
+              // Math.random because that is how seedrandom.js has worked since
+              // v1.0.
               math[rngname] = prng;
               return seed;
-            }
-
-            // Otherwise, it is a newer calling convention, so return the
-            // prng directly.
-            else {
+            } else {
+              // Otherwise, it is a newer calling convention, so return the prng
+              // directly.
               return prng;
             }
           })(
@@ -1328,7 +1322,6 @@
         const curveSegments = defaultCurveSegments;
         let k;
         let i;
-        let len;
         let ptCoord;
         let perc;
         let addedLength = 0;
@@ -1336,7 +1329,7 @@
         const point = [];
         const lastPoint = [];
         const lengthData = bezier_length_pool.newElement();
-        len = pt3.length;
+        const len = pt3.length;
         for (k = 0; k < curveSegments; k += 1) {
           perc = k / (curveSegments - 1);
           ptDistance = 0;
@@ -1408,7 +1401,6 @@
           let curveSegments = defaultCurveSegments;
           let k;
           let i;
-          let len;
           let ptCoord;
           let perc;
           let addedLength = 0;
@@ -1425,7 +1417,7 @@
             curveSegments = 2;
           }
           const bezierData = new BezierData(curveSegments);
-          len = pt3.length;
+          const len = pt3.length;
           for (k = 0; k < curveSegments; k += 1) {
             point = createSizedArray(len);
             perc = k / (curveSegments - 1);
@@ -1585,7 +1577,7 @@
 
   const bez = bezFunction();
   function dataFunctionManager() {
-    //var tCanvasHelper = createTag('canvas').getContext('2d');
+    // let tCanvasHelper = createTag('canvas').getContext('2d');
 
     function completeLayers(layers, comps, fontManager){
       let layerData;
@@ -1596,7 +1588,6 @@
       let j;
       let jLen;
       let k;
-      let kLen;
       for (i = 0; i < len; i += 1) {
         layerData = layers[i];
         if (!('ks' in layerData) || layerData.completed) {
@@ -1615,7 +1606,7 @@
             if (maskProps[j].pt.k.i) {
               convertPathsToAbsoluteValues(maskProps[j].pt.k);
             } else {
-              kLen = maskProps[j].pt.k.length;
+              const kLen = maskProps[j].pt.k.length;
               for (k = 0; k < kLen; k += 1) {
                 if (maskProps[j].pt.k[k].s) {
                   convertPathsToAbsoluteValues(maskProps[j].pt.k[k].s[0]);
@@ -1775,7 +1766,6 @@
               let j;
               let jLen;
               let k;
-              let kLen;
               let pathData;
               let paths;
               for (i = 0; i < len; i += 1) {
@@ -1901,7 +1891,6 @@
         let j;
         let jLen;
         let k;
-        let kLen;
         for (i = 0; i < len; i += 1) {
           layerData = layers[i];
           if (layerData.hasMask) {
@@ -1911,7 +1900,7 @@
               if (maskProps[j].pt.k.i) {
                 maskProps[j].pt.k.c = maskProps[j].cl;
               } else {
-                kLen = maskProps[j].pt.k.length;
+                const kLen = maskProps[j].pt.k.length;
                 for (k = 0; k < kLen; k += 1) {
                   if (maskProps[j].pt.k[k].s) {
                     maskProps[j].pt.k[k].s[0].c = maskProps[j].cl;
@@ -2112,8 +2101,6 @@
       let _pendingFonts = len;
       for (i = 0; i < len; i += 1) {
         let shouldLoadFont = true;
-        var loadedSelector;
-        var j;
         fontArr[i].loaded = false;
         fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace');
         fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif');
@@ -2121,7 +2108,7 @@
           fontArr[i].loaded = true;
           _pendingFonts -= 1;
         } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) {
-          loadedSelector = document.querySelectorAll(
+          const loadedSelector = document.querySelectorAll(
               'style[f-forigin="p"][f-family="' + fontArr[i].fFamily +
               '"], style[f-origin="3"][f-family="' + fontArr[i].fFamily + '"]');
 
@@ -2142,10 +2129,10 @@
             defs.appendChild(s);
           }
         } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) {
-          loadedSelector = document.querySelectorAll(
+          const loadedSelector = document.querySelectorAll(
               'link[f-forigin="g"], link[f-origin="1"]');
 
-          for (j = 0; j < loadedSelector.length; j++) {
+          for (let j = 0; j < loadedSelector.length; j++) {
             if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) {
               // Font is already loaded
               shouldLoadFont = false;
@@ -2162,10 +2149,10 @@
             document.body.appendChild(l);
           }
         } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) {
-          loadedSelector = document.querySelectorAll(
+          const loadedSelector = document.querySelectorAll(
               'script[f-forigin="t"], script[f-origin="2"]');
 
-          for (j = 0; j < loadedSelector.length; j++) {
+          for (let j = 0; j < loadedSelector.length; j++) {
             if (fontArr[i].fPath === loadedSelector[j].src) {
               // Font is already loaded
               shouldLoadFont = false;
@@ -2358,7 +2345,6 @@
               }
 
               let k;
-              let kLen;
               let perc;
               let jLen;
               let j;
@@ -2377,7 +2363,7 @@
                   const ind = frameNum >= nextKeyTime ?
                       bezierData.points.length - 1 :
                       0;
-                  kLen = bezierData.points[ind].point.length;
+                  const kLen = bezierData.points[ind].point.length;
                   for (k = 0; k < kLen; k += 1) {
                     newValue[k] = bezierData.points[ind].point[k];
                   }
@@ -2406,7 +2392,7 @@
                   while (flag) {
                     addedLength += bezierData.points[j].partialLength;
                     if (distanceInLine === 0 || perc === 0 || j === bezierData.points.length - 1) {
-                      kLen = bezierData.points[j].point.length;
+                      const kLen = bezierData.points[j].point.length;
                       for (k = 0; k < kLen; k += 1) {
                         newValue[k] = bezierData.points[j].point[k];
                       }
@@ -2414,7 +2400,7 @@
                     } else if (distanceInLine >= addedLength && distanceInLine < addedLength + bezierData.points[j + 1].partialLength) {
                       segmentPerc = (distanceInLine - addedLength) /
                           bezierData.points[j + 1].partialLength;
-                      kLen = bezierData.points[j].point.length;
+                      const kLen = bezierData.points[j].point.length;
                       for (k = 0; k < kLen; k += 1) {
                         newValue[k] = bezierData.points[j].point[k] +
                             (bezierData.points[j + 1].point[k] -
@@ -2911,7 +2897,9 @@
                   .rotateX(this.or.v[0]);
             }
             if (this.autoOriented) {
-              var v1, v2, frameRate = this.elem.globalData.frameRate;
+              let v1;
+              let v2;
+              const frameRate = this.elem.globalData.frameRate;
               if (this.p && this.p.keyframes && this.p.getValueAtTime) {
                 if (this.p._caching.lastFrame + this.p.offsetTime <=
                     this.p.keyframes[0].t) {
@@ -2942,7 +2930,8 @@
                   this.px.getValueAtTime && this.py.getValueAtTime) {
                 v1 = [];
                 v2 = [];
-                var px = this.px, py = this.py, frameRate;
+                const px = this.px;
+                const py = this.py;
                 if (px._caching.lastFrame + px.offsetTime <=
                     px.keyframes[0].t) {
                   v1[0] = px.getValueAtTime(
@@ -3033,7 +3022,7 @@
 
         function autoOrient() {
           //
-          // var prevP = this.getValueAtTime();
+          // let prevP = this.getValueAtTime();
         }
 
         function addDynamicProperty(prop) {
@@ -3220,8 +3209,6 @@
           let isHold;
           let j;
           let k;
-          let jLen;
-          let kLen;
           let perc;
           let vertexValue;
           const kf = this.keyframes;
@@ -3283,8 +3270,8 @@
             }
             keyPropS = keyData.s[0];
           }
-          jLen = previousValue._length;
-          kLen = keyPropS.i[0].length;
+          const jLen = previousValue._length;
+          const kLen = keyPropS.i[0].length;
           caching.lastIndex = iterationIndex;
 
           for (j = 0; j < jLen; j += 1) {
@@ -3909,7 +3896,8 @@
       if (segmentOb.e * totalModifierLength < addedLength ||
           segmentOb.s * totalModifierLength > addedLength + shapeLength) {
       } else {
-        var shapeS, shapeE;
+        let shapeS;
+        let shapeE;
         if (segmentOb.s * totalModifierLength <= addedLength) {
           shapeS = 0;
         } else {
@@ -4819,7 +4807,7 @@
 
     return ob;
   }());
-  var assetLoader = (function() {
+  let assetLoader = (function() {
     function formatResponse(xhr) {
       if (xhr.response && typeof xhr.response === 'object') {
         return xhr.response;
@@ -4864,7 +4852,7 @@
     };
   }());
 
-  var assetLoader = null;
+  assetLoader = null;
 
   function TextAnimatorProperty(textData, renderType, elem) {
     this._isFirstFrame = true;
@@ -5020,10 +5008,9 @@
     let animatorProps;
     let animatorSelector;
     let j;
-    let jLen;
     let letterValue;
 
-    jLen = animators.length;
+    const jLen = animators.length;
     let lastLetter;
 
     let mult;
@@ -5837,7 +5824,6 @@
     let lineWidth = 0;
     let maxLineWidth = 0;
     let j;
-    let jLen;
     const fontData = fontManager.getFontByName(documentData.f);
     let charData;
     let cLength = 0;
@@ -5972,7 +5958,7 @@
             fontManager.getFontByName(documentData.f).fFamily);
         cLength = newLineFlag ? 0 : charData.w * documentData.finalSize / 100;
       } else {
-        // var charWidth = fontManager.measureText(val, documentData.f,
+        // let charWidth = fontManager.measureText(val, documentData.f,
         // documentData.finalSize); tCanvasHelper.font = documentData.finalSize
         // + 'px '+ fontManager.getFontByName(documentData.f).fFamily;
         cLength = fontManager.measureText(
@@ -6056,7 +6042,7 @@
     const animators = data.a;
     let animatorData;
     let letterData;
-    jLen = animators.length;
+    const jLen = animators.length;
     let based;
     let ind;
     const indexes = [];
@@ -6090,7 +6076,8 @@
         }
       }
       data.a[j].s.totalChars = ind;
-      var currentInd = -1, newInd;
+      let currentInd = -1;
+      let newInd;
       if (animatorData.s.rn === 1) {
         for (i = 0; i < len; i += 1) {
           letterData = letters[i];
@@ -6137,7 +6124,7 @@
     this.elem.addDynamicProperty(this);
   };
 
-  var TextSelectorProp = (function() {
+  const TextSelectorProp = (function() {
     const max = Math.max;
     const min = Math.min;
     const floor = Math.floor;
@@ -6172,7 +6159,7 @@
             this.elem.textProperty.currentData.l.length) {
           this.getValue();
         }
-        // var easer = bez.getEasingCurve(this.ne.v/100,0,1-this.xe.v/100,1);
+        // let easer = bez.getEasingCurve(this.ne.v/100,0,1-this.xe.v/100,1);
         const easer =
             BezierFactory
                 .getBezierEasing(this.ne.v / 100, 0, 1 - this.xe.v / 100, 1)
@@ -6322,7 +6309,7 @@
     };
   }());
 
-  var pooling = (function() {
+  const pooling = (function() {
     function double(arr) {
       return arr.concat(createSizedArray(arr.length));
     }
@@ -6331,13 +6318,13 @@
       double: double,
     };
   }());
-  var point_pool = (function() {
+  const point_pool = (function() {
     function create() {
       return createTypedArray('float32', 2);
     }
     return pool_factory(8, create);
   }());
-  var shape_pool = (function() {
+  const shape_pool = (function() {
     function create() {
       return new ShapePath();
     }
@@ -6373,12 +6360,12 @@
       return cloned;
     }
 
-    var factory = pool_factory(4, create, release);
+    const factory = pool_factory(4, create, release);
     factory.clone = clone;
 
     return factory;
   }());
-  var shapeCollection_pool = (function() {
+  const shapeCollection_pool = (function() {
     const ob = {
       newShapeCollection: newShapeCollection,
       release: release,
@@ -6417,7 +6404,7 @@
 
     return ob;
   }());
-  var segments_length_pool = (function() {
+  const segments_length_pool = (function() {
     function create() {
       return {
         lengths: [],
@@ -6436,7 +6423,7 @@
 
     return pool_factory(8, create, release);
   }());
-  var bezier_length_pool = (function() {
+  const bezier_length_pool = (function() {
     function create() {
       return {
         addedLength: 0,
@@ -7117,7 +7104,7 @@
       this.transformCanvas.ty = 0;
     }
     this.transformCanvas.props = [this.transformCanvas.sx,0,0,0,0,this.transformCanvas.sy,0,0,0,0,1,0,this.transformCanvas.tx,this.transformCanvas.ty,0,1];
-    /*var i, len = this.elements.length;
+    /*let i, len = this.elements.length;
     for(i=0;i<len;i+=1){
         if(this.elements[i] && this.elements[i].data.ty === 0){
             this.elements[i].resize(this.globalData.transformCanvas);
@@ -7331,7 +7318,7 @@
       path.setAttribute(
           'fill', properties[i].mode === 's' ? '#000000' : '#ffffff');
       path.setAttribute('clip-rule', 'nonzero');
-      var filterID;
+      let filterID;
 
       if (properties[i].x.k !== 0) {
         maskType = 'mask';
@@ -7482,8 +7469,7 @@
   MaskElement.prototype.drawPath = function(pathData, pathNodes, viewData) {
     let pathString = ' M' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1];
     let i;
-    let len;
-    len = pathNodes._length;
+    const len = pathNodes._length;
     for(i=1;i<len;i+=1){
       // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + "
       // "+pathNodes.i[i][0]+','+pathNodes.i[i][1] + "
@@ -8131,7 +8117,7 @@
 
               // This solution doesn't work on Android when meta tag with
               // viewport attribute is set
-              /*var feColorMatrix = createNS('feColorMatrix');
+              /*let feColorMatrix = createNS('feColorMatrix');
               feColorMatrix.setAttribute('type', 'matrix');
               feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB');
               feColorMatrix.setAttribute('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0
@@ -8352,7 +8338,6 @@
     let j;
     const jLen = shapes.length;
     let k;
-    let kLen;
     let pathNodes;
     let shapeStr = '';
     for(j=0;j<jLen;j+=1){
@@ -8440,7 +8425,7 @@
   /*ICompElement.prototype.hide = function(){
       if(!this.hidden){
           this.hideElement();
-          var i,len = this.elements.length;
+          let i,len = this.elements.length;
           for( i = 0; i < len; i+=1 ){
               if(this.elements[i]){
                   this.elements[i].hide();
@@ -9096,7 +9081,8 @@
         data = this.viewData[i].v;
         pt = transform.applyToPointArray(data.v[0][0], data.v[0][1], 0);
         ctx.moveTo(pt[0], pt[1]);
-        var j, jLen = data._length;
+        let j;
+        const jLen = data._length;
         for (j = 1; j < jLen; j++) {
           pts = transform.applyToTriplePoints(
               data.o[j - 1], data.i[j], data.v[j]);
@@ -9275,7 +9261,6 @@
     let i;
     const len = styles.length;
     let j;
-    let jLen;
     for (i = 0; i < len; i += 1) {
       styles[i].closed = true;
     }
@@ -9286,7 +9271,6 @@
     let i;
     let len = arr.length - 1;
     let j;
-    let jLen;
     const ownStyles = [];
     const ownModifiers = [];
     let processedPos;
@@ -9313,7 +9297,7 @@
         if (!processedPos) {
           itemsData[i] = this.createGroupElement(arr[i]);
         } else {
-          jLen = itemsData[i].it.length;
+          const jLen = itemsData[i].it.length;
           for (j = 0; j < jLen; j += 1) {
             itemsData[i].prevViewData[j] = itemsData[i].it[j];
           }
@@ -9392,9 +9376,7 @@
     let i;
     const len = this.stylesList.length;
     let j;
-    let jLen;
     let k;
-    let kLen;
     let elems;
     let nodes;
     const renderer = this.globalData.renderer;
@@ -9431,7 +9413,7 @@
         ctx.beginPath();
       }
       renderer.ctxTransform(currentStyle.preTransforms.finalTransform.props);
-      jLen = elems.length;
+      const jLen = elems.length;
       for (j = 0; j < jLen; j += 1) {
         if (type === 'st' || type === 'gs') {
           ctx.beginPath();
@@ -9441,7 +9423,7 @@
           }
         }
         nodes = elems[j].trNodes;
-        kLen = nodes.length;
+        const kLen = nodes.length;
 
         for (k = 0; k < kLen; k += 1) {
           if (nodes[k].t == 'm') {
@@ -9578,7 +9560,7 @@
     const styleElem = itemData.style;
     if(!styleElem.grd || itemData.g._mdf || itemData.s._mdf || itemData.e._mdf || (styleData.t !== 1 && (itemData.h._mdf || itemData.a._mdf))) {
       const ctx = this.globalData.canvasContext;
-      var grd;
+      let grd;
       const pt1 = itemData.s.v;
       const pt2 = itemData.e.v;
       if (styleData.t === 1) {
@@ -9594,7 +9576,7 @@
         const dist = rad * percent;
         const x = Math.cos(ang + itemData.a.v) * dist + pt1[0];
         const y = Math.sin(ang + itemData.a.v) * dist + pt1[1];
-        var grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad);
+        grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad);
       }
 
       let i;
@@ -9871,7 +9853,7 @@
         return moduleOb;
       }());
 
-  var AnimationItem = function() {
+  const AnimationItem = function() {
     this._cbs = [];
     this.name = '';
     this.path = '';
@@ -11142,14 +11124,14 @@
             i = 0;
             j = 0;
             while(i<iterations){
-              // var rnd = BMMath.random();
+              // let rnd = BMMath.random();
               for (j = 0; j < len; j += 1) {
                 addedAmps[j] += -amp + amp * 2 * BMMath.random();
                 // addedAmps[j] += -amp + amp*2*rnd;
               }
               i += 1;
             }
-            //var rnd2 = BMMath.random();
+            // let rnd2 = BMMath.random();
             const periods = time * freq;
             const perc = periods - Math.floor(periods);
             const arr = createTypedArray('float32', len);
@@ -11289,14 +11271,12 @@
           }
 
           function key(ind) {
-            let ob;
             let i;
-            let len;
             if(!data.k.length || typeof(data.k[0]) === 'number'){
               throw new Error('The property has no keyframe at index ' + ind);
             }
             ind -= 1;
-            ob = {
+            const ob = {
               time: data.k[ind].t / elem.comp.globalData.frameRate,
               value: [],
             };
@@ -11307,7 +11287,7 @@
             }else{
               arr = data.k[ind].s;
             }
-            len = arr.length;
+            const len = arr.length;
             for(i=0;i<len;i+=1){
               ob[i] = arr[i];
               ob.value[i] = arr[i];
@@ -11370,7 +11350,7 @@
           const index = elem.data.ind;
           let hasParent = !!(elem.hierarchy && elem.hierarchy.length);
           let parent;
-          var randSeed = Math.floor(Math.random() * 1000000);
+          const randSeed = Math.floor(Math.random() * 1000000);
           const globalData = elem.globalData;
           function executeExpression(_value) {
             // globalData.pushExpression();
@@ -11992,7 +11972,7 @@
 
     TextProperty.prototype.searchExpressions = searchExpressions;
   }());
-  var ShapeExpressionInterface = (function() {
+  const ShapeExpressionInterface = (function() {
     function iterateElements(shapes,view, propertyGroup){
       const arr = [];
       let i;
@@ -12026,7 +12006,7 @@
     }
 
     function contentsInterfaceFactory(shape,view, propertyGroup){
-      let interfaces;
+      let interfaces = undefined;
       const interfaceFunction = function _interfaceFunction(value) {
         let i = 0;
         const len = interfaces.length;
@@ -12145,7 +12125,7 @@
       }
       let i;
       const len = shape.d ? shape.d.length : 0;
-      var dashOb = {};
+      const dashOb = {};
       for (i = 0; i < len; i += 1) {
         addPropertyToDashOb(i);
         view.d.dataProps[i].p.setGroupProperty(_dashPropertyGroup);
@@ -12555,7 +12535,7 @@
     }
 
     return function(shapes, view, propertyGroup) {
-      let interfaces;
+      let interfaces = undefined;
       function _interfaceFunction(value) {
         if (typeof value === 'number') {
           return interfaces[value - 1];
@@ -12577,7 +12557,7 @@
     };
   }());
 
-  var TextExpressionInterface = (function() {
+  const TextExpressionInterface = (function() {
     return function(elem) {
       let _prevValue;
       let _sourceText;
@@ -12588,10 +12568,12 @@
           const stringValue = elem.textProperty.currentData.t;
           if (stringValue !== _prevValue) {
             elem.textProperty.currentData.t = _prevValue;
+            // eslint-disable-next-line no-new-wrappers
             _sourceText = new String(stringValue);
             // If stringValue is an empty string, eval returns undefined, so it
             // has to be returned as a String primitive
             _sourceText.value =
+                // eslint-disable-next-line no-new-wrappers
                 stringValue ? stringValue : new String(stringValue);
           }
           return _sourceText;
@@ -12600,7 +12582,7 @@
       return _thisLayerFunction;
     };
   }());
-  var LayerExpressionInterface = (function() {
+  const LayerExpressionInterface = (function() {
     function toWorld(arr, time){
       const toWorldMat = new Matrix();
       toWorldMat.reset();
@@ -12669,7 +12651,7 @@
 
 
     return function(elem) {
-      let transformInterface;
+      let transformInterface = undefined;
 
       function _registerMaskInterface(maskManager) {
         _thisLayerFunction.mask = new MaskManagerInterface(maskManager, elem);
@@ -12754,7 +12736,7 @@
     };
   }());
 
-  var CompExpressionInterface = (function() {
+  const CompExpressionInterface = (function() {
     return function(comp){
         function _thisLayerFunction(name){
           let i = 0;
@@ -12781,7 +12763,7 @@
         return _thisLayerFunction;
     };
   }());
-  var TransformExpressionInterface = (function() {
+  const TransformExpressionInterface = (function() {
     return function(transform){
         function _thisFunction(name){
             switch(name){
@@ -12843,9 +12825,8 @@
           get: ExpressionPropertyInterface(transform.s),
         });
 
-        if(transform.p) {
-            var _transformFactory = ExpressionPropertyInterface(transform.p);
-        }
+        const _transformFactory =
+            transform.p ? ExpressionPropertyInterface(transform.p) : undefined;
         Object.defineProperty(_thisFunction, 'position', {
           get: function() {
             if (transform.p) {
@@ -12895,7 +12876,7 @@
         return _thisFunction;
     };
   }());
-  var ProjectInterface = (function() {
+  ProjectInterface = (function() {
     function registerComposition(comp){
       this.compositions.push(comp);
     }
@@ -12927,7 +12908,7 @@
         return _thisProjectFunction;
     };
   }());
-  var EffectsExpressionInterface = (function() {
+  const EffectsExpressionInterface = (function() {
     const ob = {
       createEffectsInterface: createEffectsInterface,
     };
@@ -12982,7 +12963,7 @@
         }
       }
 
-      var groupInterface = function(name) {
+      const groupInterface = function(name) {
         const effects = data.ef;
         let i = 0;
         const len = effects.length;
@@ -13036,7 +13017,7 @@
 
     return ob;
   }());
-  var MaskManagerInterface = (function() {
+  const MaskManagerInterface = (function() {
     function MaskInterface(mask, data) {
       this._mask = mask;
       this._data = data;
@@ -13075,7 +13056,7 @@
     return MaskManager;
   }());
 
-  var ExpressionPropertyInterface = (function() {
+  const ExpressionPropertyInterface = (function() {
     const defaultUnidimensionalValue = {pv: 0, v: 0, mult: 1};
     const defaultMultidimensionalValue = {pv: [0, 0, 0], v: [0, 0, 0], mult: 1};
 
@@ -13100,7 +13081,7 @@
             value = property.keyframes[pos - 2].s;
           }
           const valueProp = type === 'unidimensional' ?
-              new Number(value) :
+              new Number(value) :  // eslint-disable-line no-new-wrappers
               Object.assign({}, value);
           valueProp.time = property.keyframes[pos - 1].t /
               property.elem.comp.globalData.frameRate;
@@ -13119,6 +13100,7 @@
       }
       const mult = 1 / property.mult;
       let val = property.pv * mult;
+      // eslint-disable-next-line no-new-wrappers
       let expressionValue = new Number(val);
       expressionValue.value = val;
       completeProperty(expressionValue, property, 'unidimensional');
@@ -13129,6 +13111,7 @@
         }
         val = property.v * mult;
         if (expressionValue.value !== val) {
+          // eslint-disable-next-line no-new-wrappers
           expressionValue = new Number(val);
           expressionValue.value = val;
           completeProperty(expressionValue, property, 'unidimensional');
diff --git a/third_party/ruy/README.chromium b/third_party/ruy/README.chromium
index b3a52ddf..5c08a57 100644
--- a/third_party/ruy/README.chromium
+++ b/third_party/ruy/README.chromium
@@ -1,8 +1,8 @@
 Name: The ruy matrix multiplication library
 Short Name: ruy
 URL: https://github.com/google/ruy
-Version: 363f252289fb7a1fba1703d99196524698cb884d
-Date: 2023/03/31
+Version: c19139f55a94493086561288b243eaeec9d58353
+Date: 2023/05/11
 License: Apache 2
 License File: LICENSE
 Security Critical: Yes
diff --git a/third_party/tflite/README.chromium b/third_party/tflite/README.chromium
index 137efc4..eab3f2a 100644
--- a/third_party/tflite/README.chromium
+++ b/third_party/tflite/README.chromium
@@ -1,8 +1,8 @@
 Name: TensorFlow Lite
 Short Name: tflite
 URL: https://github.com/tensorflow/tensorflow
-Version: 9becb72d9b656f48de2bf00094910189434bc21e
-Date: 2023/05/08
+Version: cdd9eec54ee64375548eafeb11623c71b2733d97
+Date: 2023/05/11
 License: Apache 2.0
 License File: LICENSE
 Security Critical: Yes
diff --git a/third_party/wayland-protocols/unstable/text-input/text-input-extension-unstable-v1.xml b/third_party/wayland-protocols/unstable/text-input/text-input-extension-unstable-v1.xml
index 78a451c..ff200b9 100644
--- a/third_party/wayland-protocols/unstable/text-input/text-input-extension-unstable-v1.xml
+++ b/third_party/wayland-protocols/unstable/text-input/text-input-extension-unstable-v1.xml
@@ -24,7 +24,7 @@
     DEALINGS IN THE SOFTWARE.
   </copyright>
 
-  <interface name="zcr_text_input_extension_v1" version="9">
+  <interface name="zcr_text_input_extension_v1" version="10">
     <description summary="extends text_input to support richer operations">
       Allows a text_input to sends more variation of operations to support
       richer features, such as set_preedit_region.
@@ -57,7 +57,7 @@
 
   </interface>
 
-  <interface name="zcr_extended_text_input_v1" version="9">
+  <interface name="zcr_extended_text_input_v1" version="10">
     <description summary="extension of text_input protocol">
       The zcr_extended_text_input_v1 interface extends the text_input interface
       to support more rich operations on text_input.
@@ -373,5 +373,24 @@
       <arg name="support" type="uint" enum="surrounding_text_support" />
     </request>
 
+    <!-- Version 10 -->
+
+    <request name="set_surrounding_text_offset_utf16" since="10">
+      <description summary="Sets surrounding text's offset">
+        This updates UTF-16 offset of the immediately following
+        text_input::set_surrounding_text.
+
+        The value will be invalidated when the next set_surrounding_text
+        comes (i.e., if two consecutive set_surrounding_text is called,
+        the second set_surrounding_text's offset should be reset to 0).
+
+        Note: unlike other APIs, this is in "UTF-16" unit for Chrome's purpose,
+        because there's no way to convert UTF-8 offset to UTF-16 without
+        the original text, while sending whole text would cause performance
+        concerns.
+      </description>
+      <arg name="offset_utf16" type="uint"/>
+    </request>
+
   </interface>
 </protocol>
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index c663df59..fdbd4aa 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -339,9 +339,13 @@
 src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
 src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
 src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
-src/webgpu/shader/execution/expression/binary/f32_arithmetic.spec.ts
+src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
 src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
+src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
 src/webgpu/shader/execution/expression/binary/f32_matrix_arithmetic.spec.ts
+src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
+src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
+src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
 src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
 src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
 src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index 354ba95b..b612c443 100644
--- a/third_party/wpt_tools/README.chromium
+++ b/third_party/wpt_tools/README.chromium
@@ -1,7 +1,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: 3cc3f5123a8a092697490c66e6c69cf614523d87
+Version: 20be23d0671b32f893058dd85d1f0650be158c54
 License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
 License File: NOT_SHIPPED
 Security Critical: no
diff --git a/third_party/wpt_tools/wpt/tools/ci/tc/github_checks_output.py b/third_party/wpt_tools/wpt/tools/ci/tc/github_checks_output.py
index e982ca3..a334d39 100644
--- a/third_party/wpt_tools/wpt/tools/ci/tc/github_checks_output.py
+++ b/third_party/wpt_tools/wpt/tools/ci/tc/github_checks_output.py
@@ -1,7 +1,4 @@
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Optional, Text
+from typing import Optional, Text
 
 
 class GitHubChecksOutputter:
@@ -13,12 +10,10 @@
 
     https://docs.taskcluster.net/docs/reference/integrations/github/checks#custom-text-output-in-checks
     """
-    def __init__(self, path):
-        # type: (Text) -> None
+    def __init__(self, path: Text) -> None:
         self.path = path
 
-    def output(self, line):
-        # type: (Text) -> None
+    def output(self, line: Text) -> None:
         with open(self.path, mode="a") as f:
             f.write(line)
             f.write("\n")
@@ -27,8 +22,7 @@
 __outputter = None
 
 
-def get_gh_checks_outputter(filepath):
-    # type: (Optional[Text]) -> Optional[GitHubChecksOutputter]
+def get_gh_checks_outputter(filepath: Optional[Text]) -> Optional[GitHubChecksOutputter]:
     """Return the outputter for GitHub Checks output, if enabled.
 
     :param filepath: The filepath to write GitHub Check output information to,
diff --git a/third_party/wpt_tools/wpt/tools/conftest.py b/third_party/wpt_tools/wpt/tools/conftest.py
index 021a49f..8d1f585 100644
--- a/third_party/wpt_tools/wpt/tools/conftest.py
+++ b/third_party/wpt_tools/wpt/tools/conftest.py
@@ -1,3 +1,5 @@
+# mypy: disable-error-code="no-untyped-def"
+
 import platform
 import os
 
@@ -13,3 +15,12 @@
 
 settings.load_profile(os.getenv("HYPOTHESIS_PROFILE",
                                 "default" if impl != "PyPy" else "pypy"))
+
+
+def pytest_ignore_collect(collection_path, path, config):
+    # ignore directories which have their own tox.ini
+    assert collection_path != config.rootpath
+    if (collection_path / "tox.ini").is_file():
+        return True
+
+    return None
diff --git a/third_party/wpt_tools/wpt/tools/gitignore/gitignore.py b/third_party/wpt_tools/wpt/tools/gitignore/gitignore.py
index 2e41a9f..68c2a08 100644
--- a/third_party/wpt_tools/wpt/tools/gitignore/gitignore.py
+++ b/third_party/wpt_tools/wpt/tools/gitignore/gitignore.py
@@ -2,32 +2,18 @@
 import os
 import itertools
 from collections import defaultdict
+from typing import (Any, Dict, Iterable, List, MutableMapping, Optional, Pattern, Tuple, TypeVar,
+                    Union, cast)
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Dict
-    from typing import Iterable
-    from typing import List
-    from typing import MutableMapping
-    from typing import Optional
-    from typing import Pattern
-    from typing import Tuple
-    from typing import TypeVar
-    from typing import Union
-    from typing import cast
 
-    T = TypeVar('T')
-
+T = TypeVar('T')
 
 end_space = re.compile(r"([^\\]\s)*$")
 
 
-def fnmatch_translate(pat):
-    # type: (bytes) -> Tuple[bool, Pattern[bytes]]
+def fnmatch_translate(pat: bytes) -> Tuple[bool, Pattern[bytes]]:
     parts = []
-    seq = None  # type: Optional[int]
+    seq: Optional[int] = None
     i = 0
     any_char = b"[^/]"
     if pat[0:1] == b"/":
@@ -112,8 +98,7 @@
 pattern_re = re.compile(br".*[\*\[\?]")
 
 
-def parse_line(line):
-    # type: (bytes) -> Optional[Tuple[bool, bool, bool, Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]]]]
+def parse_line(line: bytes) -> Optional[Tuple[bool, bool, bool, Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]]]]:
     line = line.rstrip()
     if not line or line[0:1] == b"#":
         return None
@@ -130,7 +115,7 @@
     # Could make a special case for **/foo, but we don't have any patterns like that
     if not invert and not pattern_re.match(line):
         literal = True
-        pattern = tuple(line.rsplit(b"/", 1))  # type: Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]]
+        pattern: Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]] = tuple(line.rsplit(b"/", 1))
     else:
         pattern = fnmatch_translate(line)
         literal = False
@@ -139,10 +124,9 @@
 
 
 class PathFilter:
-    def __init__(self, root, extras=None, cache=None):
-        # type: (bytes, Optional[List[bytes]], Optional[MutableMapping[bytes, bool]]) -> None
+    def __init__(self, root: bytes, extras: Optional[List[bytes]] = None, cache: Optional[MutableMapping[bytes, bool]] = None) -> None:
         if root:
-            ignore_path = os.path.join(root, b".gitignore")  # type: Optional[bytes]
+            ignore_path: Optional[bytes] = os.path.join(root, b".gitignore")
         else:
             ignore_path = None
         if not ignore_path and not extras:
@@ -150,26 +134,25 @@
             return
         self.trivial = False
 
-        self.literals_file = defaultdict(dict)  # type: Dict[Optional[bytes], Dict[bytes, List[Tuple[bool, Pattern[bytes]]]]]
-        self.literals_dir = defaultdict(dict)  # type: Dict[Optional[bytes], Dict[bytes, List[Tuple[bool, Pattern[bytes]]]]]
-        self.patterns_file = []  # type: List[Tuple[Tuple[bool, Pattern[bytes]], List[Tuple[bool, Pattern[bytes]]]]]
-        self.patterns_dir = []  # type: List[Tuple[Tuple[bool, Pattern[bytes]], List[Tuple[bool, Pattern[bytes]]]]]
+        self.literals_file: Dict[Optional[bytes], Dict[bytes, List[Tuple[bool, Pattern[bytes]]]]] = defaultdict(dict)
+        self.literals_dir: Dict[Optional[bytes], Dict[bytes, List[Tuple[bool, Pattern[bytes]]]]] = defaultdict(dict)
+        self.patterns_file: List[Tuple[Tuple[bool, Pattern[bytes]], List[Tuple[bool, Pattern[bytes]]]]] = []
+        self.patterns_dir: List[Tuple[Tuple[bool, Pattern[bytes]], List[Tuple[bool, Pattern[bytes]]]]] = []
 
         if cache is None:
             cache = {}
-        self.cache = cache  # type: MutableMapping[bytes, bool]
+        self.cache: MutableMapping[bytes, bool] = cache
 
         if extras is None:
             extras = []
 
         if ignore_path and os.path.exists(ignore_path):
-            args = ignore_path, extras  # type: Tuple[Optional[bytes], List[bytes]]
+            args: Tuple[Optional[bytes], List[bytes]] = (ignore_path, extras)
         else:
             args = None, extras
         self._read_ignore(*args)
 
-    def _read_ignore(self, ignore_path, extras):
-        # type: (Optional[bytes], List[bytes]) -> None
+    def _read_ignore(self, ignore_path: Optional[bytes], extras: List[bytes]) -> None:
         if ignore_path is not None:
             with open(ignore_path, "rb") as f:
                 for line in f:
@@ -177,8 +160,7 @@
         for line in extras:
             self._read_line(line)
 
-    def _read_line(self, line):
-        # type: (bytes) -> None
+    def _read_line(self, line: bytes) -> None:
         parsed = parse_line(line)
         if not parsed:
             return
@@ -189,14 +171,13 @@
             # that we can match patterns out of order and check if they were later
             # overriden by an exclude rule
             assert not literal
-            if MYPY:
-                rule = cast(Tuple[bool, Pattern[bytes]], rule)
+            rule = cast(Tuple[bool, Pattern[bytes]], rule)
             if not dir_only:
-                rules_iter = itertools.chain(
+                rules_iter: Iterable[Tuple[Any, List[Tuple[bool, Pattern[bytes]]]]] = itertools.chain(
                     itertools.chain(*(item.items() for item in self.literals_dir.values())),
                     itertools.chain(*(item.items() for item in self.literals_file.values())),
                     self.patterns_dir,
-                    self.patterns_file)  # type: Iterable[Tuple[Any, List[Tuple[bool, Pattern[bytes]]]]]
+                    self.patterns_file)
             else:
                 rules_iter = itertools.chain(
                     itertools.chain(*(item.items() for item in self.literals_dir.values())),
@@ -206,8 +187,7 @@
                 rules[1].append(rule)
         else:
             if literal:
-                if MYPY:
-                    rule = cast(Tuple[bytes, ...], rule)
+                rule = cast(Tuple[bytes, ...], rule)
                 if len(rule) == 1:
                     dir_name, pattern = None, rule[0]  # type: Tuple[Optional[bytes], bytes]
                 else:
@@ -216,25 +196,23 @@
                 if not dir_only:
                     self.literals_file[dir_name][pattern] = []
             else:
-                if MYPY:
-                    rule = cast(Tuple[bool, Pattern[bytes]], rule)
+                rule = cast(Tuple[bool, Pattern[bytes]], rule)
                 self.patterns_dir.append((rule, []))
                 if not dir_only:
                     self.patterns_file.append((rule, []))
 
     def filter(self,
-               iterator  # type: Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
-               ):
-        # type: (...) -> Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
-        empty = {}  # type: Dict[Any, Any]
+               iterator: Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
+               ) -> Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]:
+        empty: Dict[Any, Any] = {}
         for dirpath, dirnames, filenames in iterator:
             orig_dirpath = dirpath
             path_sep = os.path.sep.encode()
             if path_sep != b"/":
                 dirpath = dirpath.replace(path_sep, b"/")
 
-            keep_dirs = []  # type: List[Tuple[bytes, T]]
-            keep_files = []  # type: List[Tuple[bytes, T]]
+            keep_dirs: List[Tuple[bytes, T]] = []
+            keep_files: List[Tuple[bytes, T]] = []
 
             for iter_items, literals, patterns, target, suffix in [
                     (dirnames, self.literals_dir, self.patterns_dir, keep_dirs, b"/"),
@@ -278,15 +256,13 @@
             yield orig_dirpath, dirnames, keep_files
 
     def __call__(self,
-                 iterator  # type: Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
-                 ):
-        # type: (...) -> Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
+                 iterator: Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
+                 ) -> Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]:
         if self.trivial:
             return iterator
 
         return self.filter(iterator)
 
 
-def has_ignore(dirpath):
-    # type: (bytes) -> bool
+def has_ignore(dirpath: bytes) -> bool:
     return os.path.exists(os.path.join(dirpath, b".gitignore"))
diff --git a/third_party/wpt_tools/wpt/tools/lint/fnmatch.py b/third_party/wpt_tools/wpt/tools/lint/fnmatch.py
index 143cb436..4148ed7 100644
--- a/third_party/wpt_tools/wpt/tools/lint/fnmatch.py
+++ b/third_party/wpt_tools/wpt/tools/lint/fnmatch.py
@@ -1,26 +1,18 @@
 import fnmatch as _stdlib_fnmatch
 import os
-
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Iterable
-    from typing import List
-    from typing import Text
+from typing import Iterable, List, Text
 
 
 __all__ = ["fnmatch", "fnmatchcase", "filter", "translate"]
 
 
-def fnmatch(name, pat):
-    # type: (Text, Text) -> bool
+def fnmatch(name: Text, pat: Text) -> bool:
     name = os.path.normcase(name)
     pat = os.path.normcase(pat)
     return fnmatchcase(name, pat)
 
 
-def fnmatchcase(name, pat):
-    # type: (Text, Text) -> bool
+def fnmatchcase(name: Text, pat: Text) -> bool:
     if '?' not in pat and '[' not in pat:
         wildcards = pat.count("*")
         if wildcards == 0:
@@ -32,8 +24,7 @@
     return _stdlib_fnmatch.fnmatchcase(name, pat)
 
 
-def filter(names, pat):
-    # type: (Iterable[Text], Text) -> List[Text]
+def filter(names: Iterable[Text], pat: Text) -> List[Text]:
     return [n for n in names if fnmatch(n, pat)]
 
 
diff --git a/third_party/wpt_tools/wpt/tools/lint/lint.py b/third_party/wpt_tools/wpt/tools/lint/lint.py
index fbc6634..9fc78d9b 100644
--- a/third_party/wpt_tools/wpt/tools/lint/lint.py
+++ b/third_party/wpt_tools/wpt/tools/lint/lint.py
@@ -9,10 +9,17 @@
 import subprocess
 import sys
 import tempfile
-
 from collections import defaultdict
+from typing import (Any, Callable, Dict, IO, Iterable, List, Optional, Sequence, Set, Text, Tuple,
+                    Type, TypeVar)
+
 from urllib.parse import urlsplit, urljoin
 
+try:
+    from xml.etree import cElementTree as ElementTree
+except ImportError:
+    from xml.etree import ElementTree as ElementTree  # type: ignore
+
 from . import fnmatch
 from . import rules
 from .. import localpaths
@@ -23,48 +30,26 @@
 
 from ..manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars, get_any_variants
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Callable
-    from typing import Dict
-    from typing import IO
-    from typing import Iterable
-    from typing import List
-    from typing import Optional
-    from typing import Sequence
-    from typing import Set
-    from typing import Text
-    from typing import Tuple
-    from typing import Type
-    from typing import TypeVar
 
-    # The Ignorelist is a two level dictionary. The top level is indexed by
-    # error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of
-    # file patterns (e.g. 'foo/*') to a set of specific line numbers for the
-    # exception. The line numbers are optional; if missing the entire file
-    # ignores the error.
-    Ignorelist = Dict[str, Dict[str, Set[Optional[int]]]]
+# The Ignorelist is a two level dictionary. The top level is indexed by
+# error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of
+# file patterns (e.g. 'foo/*') to a set of specific line numbers for the
+# exception. The line numbers are optional; if missing the entire file
+# ignores the error.
+Ignorelist = Dict[str, Dict[str, Set[Optional[int]]]]
 
-    # Define an arbitrary typevar
-    T = TypeVar("T")
-
-    try:
-        from xml.etree import cElementTree as ElementTree
-    except ImportError:
-        from xml.etree import ElementTree as ElementTree  # type: ignore
+# Define an arbitrary typevar
+T = TypeVar("T")
 
 
-logger = None  # type: Optional[logging.Logger]
+logger: Optional[logging.Logger] = None
 
 
-def setup_logging(prefix=False):
-    # type: (bool) -> None
+def setup_logging(prefix: bool = False) -> None:
     global logger
     if logger is None:
         logger = logging.getLogger(os.path.basename(os.path.splitext(__file__)[0]))
-        handler = logging.StreamHandler(sys.stdout)  # type: logging.Handler
+        handler: logging.Handler = logging.StreamHandler(sys.stdout)
         # Only add a handler if the parent logger is missing a handler
         parent = logger.parent
         assert isinstance(parent, logging.Logger)
@@ -98,8 +83,7 @@
 %s: %s"""
 
 
-def all_filesystem_paths(repo_root, subdir=None):
-    # type: (Text, Optional[Text]) -> Iterable[Text]
+def all_filesystem_paths(repo_root: Text, subdir: Optional[Text] = None) -> Iterable[Text]:
     path_filter = PathFilter(repo_root.encode("utf8"),
                              extras=[b".git/"])
     if subdir:
@@ -116,8 +100,7 @@
             yield path.decode("utf8")
 
 
-def _all_files_equal(paths):
-    # type: (Iterable[Text]) -> bool
+def _all_files_equal(paths: Iterable[Text]) -> bool:
     """
     Checks all the paths are files that are byte-for-byte identical
 
@@ -155,22 +138,19 @@
     return True
 
 
-def check_path_length(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_path_length(repo_root: Text, path: Text) -> List[rules.Error]:
     if len(path) + 1 > 150:
         return [rules.PathLength.error(path, (path, len(path) + 1))]
     return []
 
 
-def check_file_type(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_file_type(repo_root: Text, path: Text) -> List[rules.Error]:
     if os.path.islink(path):
         return [rules.FileType.error(path, (path, "symlink"))]
     return []
 
 
-def check_worker_collision(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_worker_collision(repo_root: Text, path: Text) -> List[rules.Error]:
     endings = [(".any.html", ".any.js"),
                (".any.worker.html", ".any.js"),
                (".worker.html", ".worker.js")]
@@ -180,8 +160,7 @@
     return []
 
 
-def check_gitignore_file(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_gitignore_file(repo_root: Text, path: Text) -> List[rules.Error]:
     if not path.endswith(".gitignore"):
         return []
 
@@ -199,23 +178,20 @@
     return [rules.GitIgnoreFile.error(path)]
 
 
-def check_mojom_js(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_mojom_js(repo_root: Text, path: Text) -> List[rules.Error]:
     if path.endswith(".mojom.js"):
         return [rules.MojomJSFile.error(path)]
     return []
 
 
-def check_ahem_copy(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_ahem_copy(repo_root: Text, path: Text) -> List[rules.Error]:
     lpath = path.lower()
     if "ahem" in lpath and lpath.endswith(".ttf"):
         return [rules.AhemCopy.error(path)]
     return []
 
 
-def check_tentative_directories(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_tentative_directories(repo_root: Text, path: Text) -> List[rules.Error]:
     path_parts = path.split(os.path.sep)
     for directory in path_parts[:-1]:
         if "tentative" in directory and directory != "tentative":
@@ -223,8 +199,7 @@
     return []
 
 
-def check_git_ignore(repo_root, paths):
-    # type: (Text, List[Text]) -> List[rules.Error]
+def check_git_ignore(repo_root: Text, paths: List[Text]) -> List[rules.Error]:
     errors = []
 
     with tempfile.TemporaryFile('w+', newline='') as f:
@@ -253,8 +228,7 @@
 w3c_dev_re = re.compile(r"https?\:\/\/dev\.w3c?\.org\/[^/?#]+\/([^/?#]+)")
 
 
-def check_unique_testharness_basenames(repo_root, paths):
-    # type: (Text, List[Text]) -> List[rules.Error]
+def check_unique_testharness_basenames(repo_root: Text, paths: List[Text]) -> List[rules.Error]:
     """
     Checks that all testharness files have unique basename paths.
 
@@ -288,9 +262,8 @@
     return errors
 
 
-def check_unique_case_insensitive_paths(repo_root, paths):
-    # type: (Text, List[Text]) -> List[rules.Error]
-    seen = {}  # type: Dict[Text, Text]
+def check_unique_case_insensitive_paths(repo_root: Text, paths: List[Text]) -> List[rules.Error]:
+    seen: Dict[Text, Text] = {}
     errors = []
     for path in paths:
         lower_path = path.lower()
@@ -302,8 +275,7 @@
     return errors
 
 
-def parse_ignorelist(f):
-    # type: (IO[Text]) -> Tuple[Ignorelist, Set[Text]]
+def parse_ignorelist(f: IO[Text]) -> Tuple[Ignorelist, Set[Text]]:
     """
     Parse the ignorelist file given by `f`, and return the parsed structure.
 
@@ -311,8 +283,8 @@
               skipped by the linter (i.e. have a '*' entry).
     """
 
-    data = defaultdict(lambda:defaultdict(set))  # type: Ignorelist
-    skipped_files = set()  # type: Set[Text]
+    data: Ignorelist = defaultdict(lambda:defaultdict(set))
+    skipped_files: Set[Text] = set()
 
     for line in f:
         line = line.strip()
@@ -322,7 +294,7 @@
 
         if len(parts) == 2:
             error_types_s, file_match = parts
-            line_number = None  # type: Optional[int]
+            line_number: Optional[int] = None
         else:
             error_types_s, file_match, line_number_s = parts
             line_number = int(line_number_s)
@@ -339,8 +311,7 @@
     return data, skipped_files
 
 
-def filter_ignorelist_errors(data, errors):
-    # type: (Ignorelist, Sequence[rules.Error]) -> List[rules.Error]
+def filter_ignorelist_errors(data: Ignorelist, errors: Sequence[rules.Error]) -> List[rules.Error]:
     """
     Filter out those errors that are ignored in `data`.
     """
@@ -383,9 +354,8 @@
             rules.AssertPreconditionRegexp]]
 
 
-def check_regexp_line(repo_root, path, f):
-    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
-    errors = []  # type: List[rules.Error]
+def check_regexp_line(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
+    errors: List[rules.Error] = []
 
     applicable_regexps = [regexp for regexp in regexps if regexp.applies(path)]
 
@@ -397,11 +367,10 @@
     return errors
 
 
-def check_parsed(repo_root, path, f):
-    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
+def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
     source_file = SourceFile(repo_root, path, "/", contents=f.read())
 
-    errors = []  # type: List[rules.Error]
+    errors: List[rules.Error] = []
 
     if path.startswith("css/"):
         if (source_file.type != "support" and
@@ -462,9 +431,9 @@
         if timeout_value != "long":
             errors.append(rules.InvalidTimeout.error(path, (timeout_value,)))
 
-    required_elements = []  # type: List[Text]
+    required_elements: List[Text] = []
 
-    testharnessreport_nodes = []  # type: List[ElementTree.Element]
+    testharnessreport_nodes: List[ElementTree.Element] = []
     if source_file.testharness_nodes:
         test_type = source_file.manifest_items()[0]
         if test_type not in ("testharness", "manual"):
@@ -495,7 +464,7 @@
                                                         "timeout": len(source_file.timeout_nodes) > 0}.items()
                                  if value)
 
-    testdriver_vendor_nodes = []  # type: List[ElementTree.Element]
+    testdriver_vendor_nodes: List[ElementTree.Element] = []
     if source_file.testdriver_nodes:
         if len(source_file.testdriver_nodes) > 1:
             errors.append(rules.MultipleTestdriver.error(path))
@@ -542,8 +511,7 @@
     for element in source_file.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src]"):
         src = element.attrib["src"]
 
-        def incorrect_path(script, src):
-            # type: (Text, Text) -> bool
+        def incorrect_path(script: Text, src: Text) -> bool:
             return (script == src or
                 ("/%s" % script in src and src != "/resources/%s" % script))
 
@@ -573,20 +541,17 @@
 
 class ASTCheck(metaclass=abc.ABCMeta):
     @abc.abstractproperty
-    def rule(self):
-        # type: () -> Type[rules.Rule]
+    def rule(self) -> Type[rules.Rule]:
         pass
 
     @abc.abstractmethod
-    def check(self, root):
-        # type: (ast.AST) -> List[int]
+    def check(self, root: ast.AST) -> List[int]:
         pass
 
 class OpenModeCheck(ASTCheck):
     rule = rules.OpenNoMode
 
-    def check(self, root):
-        # type: (ast.AST) -> List[int]
+    def check(self, root: ast.AST) -> List[int]:
         errors = []
         for node in ast.walk(root):
             if isinstance(node, ast.Call):
@@ -598,8 +563,7 @@
 
 ast_checkers = [item() for item in [OpenModeCheck]]
 
-def check_python_ast(repo_root, path, f):
-    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
+def check_python_ast(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
     if not path.endswith(".py"):
         return []
 
@@ -619,8 +583,7 @@
 broken_python_metadata = re.compile(br"#\s*META:")
 
 
-def check_global_metadata(value):
-    # type: (bytes) -> Iterable[Tuple[Type[rules.Rule], Tuple[Any, ...]]]
+def check_global_metadata(value: bytes) -> Iterable[Tuple[Type[rules.Rule], Tuple[Any, ...]]]:
     global_values = {item.strip().decode("utf8") for item in value.split(b",") if item.strip()}
 
     # TODO: this could check for duplicates and such
@@ -629,8 +592,7 @@
             yield (rules.UnknownGlobalMetadata, ())
 
 
-def check_script_metadata(repo_root, path, f):
-    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
+def check_script_metadata(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
     if path.endswith((".worker.js", ".any.js")):
         meta_re = js_meta_re
         broken_metadata = broken_js_metadata
@@ -680,8 +642,7 @@
                                 flags=re.IGNORECASE)
 
 
-def check_ahem_system_font(repo_root, path, f):
-    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
+def check_ahem_system_font(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
     if not path.endswith((".html", ".htm", ".xht", ".xhtml")):
         return []
     contents = f.read()
@@ -691,8 +652,7 @@
     return errors
 
 
-def check_path(repo_root, path):
-    # type: (Text, Text) -> List[rules.Error]
+def check_path(repo_root: Text, path: Text) -> List[rules.Error]:
     """
     Runs lints that check the file path.
 
@@ -707,8 +667,7 @@
     return errors
 
 
-def check_all_paths(repo_root, paths):
-    # type: (Text, List[Text]) -> List[rules.Error]
+def check_all_paths(repo_root: Text, paths: List[Text]) -> List[rules.Error]:
     """
     Runs lints that check all paths globally.
 
@@ -723,8 +682,7 @@
     return errors
 
 
-def check_file_contents(repo_root, path, f=None):
-    # type: (Text, Text, Optional[IO[bytes]]) -> List[rules.Error]
+def check_file_contents(repo_root: Text, path: Text, f: Optional[IO[bytes]] = None) -> List[rules.Error]:
     """
     Runs lints that check the file contents.
 
@@ -743,13 +701,11 @@
         return errors
 
 
-def check_file_contents_apply(args):
-    # type: (Tuple[Text, Text]) -> List[rules.Error]
+def check_file_contents_apply(args: Tuple[Text, Text]) -> List[rules.Error]:
     return check_file_contents(*args)
 
 
-def output_errors_text(log, errors):
-    # type: (Callable[[Any], None], List[rules.Error]) -> None
+def output_errors_text(log: Callable[[Any], None], errors: List[rules.Error]) -> None:
     for error_type, description, path, line_number in errors:
         pos_string = path
         if line_number:
@@ -757,8 +713,7 @@
         log(f"{pos_string}: {description} ({error_type})")
 
 
-def output_errors_markdown(log, errors):
-    # type: (Callable[[Any], None], List[rules.Error]) -> None
+def output_errors_markdown(log: Callable[[Any], None], errors: List[rules.Error]) -> None:
     if not errors:
         return
     heading = """Got lint errors:
@@ -774,8 +729,7 @@
         log(f"{error_type} | {pos_string} | {description} |")
 
 
-def output_errors_json(log, errors):
-    # type: (Callable[[Any], None], List[rules.Error]) -> None
+def output_errors_json(log: Callable[[Any], None], errors: List[rules.Error]) -> None:
     for error_type, error, path, line_number in errors:
         # We use 'print' rather than the log function to ensure that the output
         # is valid JSON (e.g. with no logger preamble).
@@ -783,8 +737,7 @@
                           "rule": error_type, "message": error}))
 
 
-def output_errors_github_checks(outputter, errors, first_reported):
-    # type: (GitHubChecksOutputter, List[rules.Error], bool) -> None
+def output_errors_github_checks(outputter: GitHubChecksOutputter, errors: List[rules.Error], first_reported: bool) -> None:
     """Output errors to the GitHub Checks output markdown format.
 
     :param outputter: the GitHub Checks outputter
@@ -803,8 +756,7 @@
     output_errors_text(outputter.output, errors)
 
 
-def output_error_count(error_count):
-    # type: (Dict[Text, int]) -> None
+def output_error_count(error_count: Dict[Text, int]) -> None:
     if not error_count:
         return
 
@@ -818,15 +770,13 @@
         logger.info("There were %d errors (%s)" % (count, by_type))
 
 
-def changed_files(wpt_root):
-    # type: (Text) -> List[Text]
+def changed_files(wpt_root: Text) -> List[Text]:
     revish = testfiles.get_revish(revish=None)
     changed, _ = testfiles.files_changed(revish, None, include_uncommitted=True, include_new=True)
     return [os.path.relpath(item, wpt_root) for item in changed]
 
 
-def lint_paths(kwargs, wpt_root):
-    # type: (Dict[Text, Any], Text) -> List[Text]
+def lint_paths(kwargs: Dict[Text, Any], wpt_root: Text) -> List[Text]:
     if kwargs.get("paths"):
         paths = []
         for path in kwargs.get("paths", []):
@@ -861,8 +811,7 @@
     return paths
 
 
-def create_parser():
-    # type: () -> argparse.ArgumentParser
+def create_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser()
     parser.add_argument("paths", nargs="*",
                         help="List of paths to lint")
@@ -887,8 +836,7 @@
     return parser
 
 
-def main(**kwargs):
-    # type: (**Any) -> int
+def main(**kwargs: Any) -> int:
 
     assert logger is not None
     if kwargs.get("json") and kwargs.get("markdown"):
@@ -919,9 +867,13 @@
 MIN_FILES_FOR_PARALLEL = 80
 
 
-def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_outputter=None, jobs=0):
-    # type: (Text, List[Text], Text, Optional[List[Text]], Optional[GitHubChecksOutputter], int) -> int
-    error_count = defaultdict(int)  # type: Dict[Text, int]
+def lint(repo_root: Text,
+         paths: List[Text],
+         output_format: Text,
+         ignore_glob: Optional[List[Text]] = None,
+         github_checks_outputter: Optional[GitHubChecksOutputter] = None,
+         jobs: int = 0) -> int:
+    error_count: Dict[Text, int] = defaultdict(int)
     last = None
 
     if jobs == 0:
@@ -942,8 +894,7 @@
                      "markdown": output_errors_markdown,
                      "normal": output_errors_text}[output_format]
 
-    def process_errors(errors):
-        # type: (List[rules.Error]) -> Optional[Tuple[Text, Text]]
+    def process_errors(errors: List[rules.Error]) -> Optional[Tuple[Text, Text]]:
         """
         Filters and prints the errors, and updates the ``error_count`` object.
 
diff --git a/third_party/wpt_tools/wpt/tools/lint/rules.py b/third_party/wpt_tools/wpt/tools/lint/rules.py
index d5876cd..88b7b6d 100644
--- a/third_party/wpt_tools/wpt/tools/lint/rules.py
+++ b/third_party/wpt_tools/wpt/tools/lint/rules.py
@@ -2,42 +2,30 @@
 import inspect
 import os
 import re
-
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any, List, Match, Optional, Pattern, Text, Tuple, cast
-    Error = Tuple[str, str, str, Optional[int]]
+from typing import Any, List, Match, Optional, Pattern, Text, Tuple, cast
 
 
-def collapse(text):
-    # type: (Text) -> Text
+Error = Tuple[str, str, str, Optional[int]]
+
+def collapse(text: Text) -> Text:
     return inspect.cleandoc(str(text)).replace("\n", " ")
 
 
 class Rule(metaclass=abc.ABCMeta):
     @abc.abstractproperty
-    def name(self):
-        # type: () -> Text
+    def name(self) -> Text:
         pass
 
     @abc.abstractproperty
-    def description(self):
-        # type: () -> Text
+    def description(self) -> Text:
         pass
 
-    to_fix = None  # type: Optional[Text]
+    to_fix: Optional[Text] = None
 
     @classmethod
-    def error(cls, path, context=(), line_no=None):
-        # type: (Text, Tuple[Any, ...], Optional[int]) -> Error
-        if MYPY:
-            name = cast(str, cls.name)
-            description = cast(str, cls.description)
-        else:
-            name = cls.name
-            description = cls.description
-        description = description % context
+    def error(cls, path: Text, context: Tuple[Any, ...] = (), line_no: Optional[int] = None) -> Error:
+        name = cast(str, cls.name)
+        description = cast(str, cls.description) % context
         return (name, description, path, line_no)
 
 
@@ -353,33 +341,27 @@
 
 class Regexp(metaclass=abc.ABCMeta):
     @abc.abstractproperty
-    def pattern(self):
-        # type: () -> bytes
+    def pattern(self) -> bytes:
         pass
 
     @abc.abstractproperty
-    def name(self):
-        # type: () -> Text
+    def name(self) -> Text:
         pass
 
     @abc.abstractproperty
-    def description(self):
-        # type: () -> Text
+    def description(self) -> Text:
         pass
 
-    file_extensions = None  # type: Optional[List[Text]]
+    file_extensions: Optional[List[Text]] = None
 
-    def __init__(self):
-        # type: () -> None
-        self._re = re.compile(self.pattern)  # type: Pattern[bytes]
+    def __init__(self) -> None:
+        self._re: Pattern[bytes] = re.compile(self.pattern)
 
-    def applies(self, path):
-        # type: (Text) -> bool
+    def applies(self, path: Text) -> bool:
         return (self.file_extensions is None or
                 os.path.splitext(path)[1] in self.file_extensions)
 
-    def search(self, line):
-        # type: (bytes) -> Optional[Match[bytes]]
+    def search(self, line: bytes) -> Optional[Match[bytes]]:
         return self._re.search(line)
 
 
diff --git a/third_party/wpt_tools/wpt/tools/manifest/XMLParser.py b/third_party/wpt_tools/wpt/tools/manifest/XMLParser.py
index 21be8a26..8dcdb45 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/XMLParser.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/XMLParser.py
@@ -1,31 +1,20 @@
-from os.path import dirname, join
-
 from collections import OrderedDict
-
+from typing import Dict, List, Optional, Text, Union
+from os.path import dirname, join
 from xml.parsers import expat
 import xml.etree.ElementTree as etree  # noqa: N813
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Dict
-    from typing import List
-    from typing import Optional
-    from typing import Text
-    from typing import Union
 
 _catalog = join(dirname(__file__), "catalog")
 
-def _wrap_error(e):
-    # type: (expat.error) -> etree.ParseError
+def _wrap_error(e: expat.error) -> etree.ParseError:
     err = etree.ParseError(e)
     err.code = e.code
     err.position = e.lineno, e.offset
     raise err
 
-_names = {}  # type: Dict[Text, Text]
-def _fixname(key):
-    # type: (Text) -> Text
+_names: Dict[Text, Text] = {}
+def _fixname(key: Text) -> Text:
     try:
         name = _names[key]
     except KeyError:
@@ -36,7 +25,7 @@
     return name
 
 
-_undefined_entity_code = expat.errors.codes[expat.errors.XML_ERROR_UNDEFINED_ENTITY]  # type: int
+_undefined_entity_code: int = expat.errors.codes[expat.errors.XML_ERROR_UNDEFINED_ENTITY]
 
 
 class XMLParser:
@@ -48,8 +37,7 @@
     (therefore allowing XHTML entities) and supports all encodings
     Python does, rather than just those supported by expat.
     """
-    def __init__(self, encoding=None):
-        # type: (Optional[Text]) -> None
+    def __init__(self, encoding: Optional[Text] = None) -> None:
         self._parser = expat.ParserCreate(encoding, "}")
         self._target = etree.TreeBuilder()
         # parser settings
@@ -64,34 +52,29 @@
         self._parser.ExternalEntityRefHandler = self._external
         self._parser.SkippedEntityHandler = self._skipped
         # used for our horrible re-encoding hack
-        self._fed_data = []  # type: Optional[List[bytes]]
-        self._read_encoding = None  # type: Optional[Text]
+        self._fed_data: Optional[List[bytes]] = []
+        self._read_encoding: Optional[Text] = None
 
-    def _xml_decl(self, version, encoding, standalone):
-        # type: (Text, Optional[Text], int) -> None
+    def _xml_decl(self, version: Text, encoding: Optional[Text], standalone: int) -> None:
         self._read_encoding = encoding
 
-    def _start(self, tag, attrib_in):
-        # type: (Text, List[str]) -> etree.Element
+    def _start(self, tag: Text, attrib_in: List[str]) -> etree.Element:
         assert isinstance(tag, str)
         self._fed_data = None
         tag = _fixname(tag)
-        attrib = OrderedDict()  # type: Dict[Union[bytes, Text], Union[bytes, Text]]
+        attrib: Dict[Union[bytes, Text], Union[bytes, Text]] = OrderedDict()
         if attrib_in:
             for i in range(0, len(attrib_in), 2):
                 attrib[_fixname(attrib_in[i])] = attrib_in[i+1]
         return self._target.start(tag, attrib)
 
-    def _data(self, text):
-        # type: (Text) -> None
+    def _data(self, text: Text) -> None:
         self._target.data(text)
 
-    def _end(self, tag):
-        # type: (Text) -> etree.Element
+    def _end(self, tag: Text) -> etree.Element:
         return self._target.end(_fixname(tag))
 
-    def _external(self, context, base, system_id, public_id):
-        # type: (Text, Optional[Text], Optional[Text], Optional[Text]) -> bool
+    def _external(self, context: Text, base: Optional[Text], system_id: Optional[Text], public_id: Optional[Text]) -> bool:
         if public_id in {
                 "-//W3C//DTD XHTML 1.0 Transitional//EN",
                 "-//W3C//DTD XHTML 1.1//EN",
@@ -112,8 +95,7 @@
 
         return True
 
-    def _skipped(self, name, is_parameter_entity):
-        # type: (Text, bool) -> None
+    def _skipped(self, name: Text, is_parameter_entity: bool) -> None:
         err = expat.error("undefined entity %s: line %d, column %d" %
                           (name, self._parser.ErrorLineNumber,
                            self._parser.ErrorColumnNumber))
@@ -122,8 +104,7 @@
         err.offset = self._parser.ErrorColumnNumber
         raise err
 
-    def feed(self, data):
-        # type: (bytes) -> None
+    def feed(self, data: bytes) -> None:
         if self._fed_data is not None:
             self._fed_data.append(data)
         try:
@@ -141,8 +122,7 @@
                 self._fed_data = None
                 self.feed(xml)
 
-    def close(self):
-        # type: () -> etree.Element
+    def close(self) -> etree.Element:
         try:
             self._parser.Parse("", True)
         except expat.error as v:
diff --git a/third_party/wpt_tools/wpt/tools/manifest/download.py b/third_party/wpt_tools/wpt/tools/manifest/download.py
index 4a8b6fc..8527fb2 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/download.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/download.py
@@ -5,6 +5,7 @@
 import io
 import os
 from datetime import datetime, timedelta
+from typing import Any, Callable, List, Optional, Text
 from urllib.request import urlopen
 
 try:
@@ -16,14 +17,6 @@
 
 from . import log
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Callable
-    from typing import List
-    from typing import Optional
-    from typing import Text
 
 here = os.path.dirname(__file__)
 
@@ -31,13 +24,11 @@
 logger = log.get_logger()
 
 
-def abs_path(path):
-    # type: (Text) -> Text
+def abs_path(path: Text) -> Text:
     return os.path.abspath(os.path.expanduser(path))
 
 
-def should_download(manifest_path, rebuild_time=timedelta(days=5)):
-    # type: (Text, timedelta) -> bool
+def should_download(manifest_path: Text, rebuild_time: timedelta = timedelta(days=5)) -> bool:
     if not os.path.exists(manifest_path):
         return True
     mtime = datetime.fromtimestamp(os.path.getmtime(manifest_path))
@@ -47,10 +38,9 @@
     return False
 
 
-def merge_pr_tags(repo_root, max_count=50):
-    # type: (Text, int) -> List[Text]
+def merge_pr_tags(repo_root: Text, max_count: int = 50) -> List[Text]:
     gitfunc = git(repo_root)
-    tags = []  # type: List[Text]
+    tags: List[Text] = []
     if gitfunc is None:
         return tags
     for line in gitfunc("log", "--format=%D", "--max-count=%s" % max_count).split("\n"):
@@ -60,8 +50,7 @@
     return tags
 
 
-def score_name(name):
-    # type: (Text) -> Optional[int]
+def score_name(name: Text) -> Optional[int]:
     """Score how much we like each filename, lower wins, None rejects"""
 
     # Accept both ways of naming the manifest asset, even though
@@ -76,8 +65,7 @@
     return None
 
 
-def github_url(tags):
-    # type: (List[Text]) -> Optional[List[Text]]
+def github_url(tags: List[Text]) -> Optional[List[Text]]:
     for tag in tags:
         url = "https://api.github.com/repos/web-platform-tests/wpt/releases/tags/%s" % tag
         try:
@@ -108,12 +96,11 @@
 
 
 def download_manifest(
-        manifest_path,  # type: Text
-        tags_func,  # type: Callable[[], List[Text]]
-        url_func,  # type: Callable[[List[Text]], Optional[List[Text]]]
-        force=False  # type: bool
-):
-    # type: (...) -> bool
+        manifest_path: Text,
+        tags_func: Callable[[], List[Text]],
+        url_func: Callable[[List[Text]], Optional[List[Text]]],
+        force: bool = False
+) -> bool:
     if not force and not should_download(manifest_path):
         return False
 
@@ -178,8 +165,7 @@
     return True
 
 
-def create_parser():
-    # type: () -> argparse.ArgumentParser
+def create_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "-p", "--path", type=abs_path, help="Path to manifest file.")
@@ -191,14 +177,12 @@
     return parser
 
 
-def download_from_github(path, tests_root, force=False):
-    # type: (Text, Text, bool) -> bool
+def download_from_github(path: Text, tests_root: Text, force: bool = False) -> bool:
     return download_manifest(path, lambda: merge_pr_tags(tests_root), github_url,
                              force=force)
 
 
-def run(**kwargs):
-    # type: (**Any) -> int
+def run(**kwargs: Any) -> int:
     if kwargs["path"] is None:
         path = os.path.join(kwargs["tests_root"], "MANIFEST.json")
     else:
diff --git a/third_party/wpt_tools/wpt/tools/manifest/item.py b/third_party/wpt_tools/wpt/tools/manifest/item.py
index f798ec2a..500ca10 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/item.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/item.py
@@ -1,19 +1,18 @@
 import os.path
-from inspect import isabstract
-from urllib.parse import urljoin, urlparse, parse_qs
 from abc import ABCMeta, abstractproperty
+from inspect import isabstract
+from typing import (Any, Dict, Hashable, List, Optional, Sequence, Text, Tuple, Type,
+                    TYPE_CHECKING, Union, cast)
+from urllib.parse import urljoin, urlparse, parse_qs
 
 from .utils import to_os_path
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any, Dict, Hashable, List, Optional, Sequence, Text, Tuple, Type, Union, cast
+if TYPE_CHECKING:
     from .manifest import Manifest
-    Fuzzy = Dict[Optional[Tuple[str, str, str]], List[int]]
-    PageRanges = Dict[str, List[int]]
 
-item_types = {}  # type: Dict[str, Type[ManifestItem]]
+Fuzzy = Dict[Optional[Tuple[str, str, str]], List[int]]
+PageRanges = Dict[str, List[int]]
+item_types: Dict[str, Type["ManifestItem"]] = {}
 
 
 class ManifestItemMeta(ABCMeta):
@@ -21,18 +20,13 @@
     item_types dictionary according to the value of their item_type
     attribute, and otherwise behaves like an ABCMeta."""
 
-    def __new__(cls, name, bases, attrs):
-        # type: (Type[ManifestItemMeta], str, Tuple[type], Dict[str, Any]) -> ManifestItemMeta
+    def __new__(cls: Type["ManifestItemMeta"], name: str, bases: Tuple[type], attrs: Dict[str, Any]) -> "ManifestItemMeta":
         inst = super().__new__(cls, name, bases, attrs)
         if isabstract(inst):
             return inst
 
         assert issubclass(inst, ManifestItem)
-        if MYPY:
-            item_type = cast(str, inst.item_type)
-        else:
-            assert isinstance(inst.item_type, str)
-            item_type = inst.item_type
+        item_type = cast(str, inst.item_type)
 
         item_types[item_type] = inst
 
@@ -42,58 +36,48 @@
 class ManifestItem(metaclass=ManifestItemMeta):
     __slots__ = ("_tests_root", "path")
 
-    def __init__(self, tests_root, path):
-        # type: (Text, Text) -> None
+    def __init__(self, tests_root: Text, path: Text) -> None:
         self._tests_root = tests_root
         self.path = path
 
     @abstractproperty
-    def id(self):
-        # type: () -> Text
+    def id(self) -> Text:
         """The test's id (usually its url)"""
         pass
 
     @abstractproperty
-    def item_type(self):
-        # type: () -> str
+    def item_type(self) -> str:
         """The item's type"""
         pass
 
     @property
-    def path_parts(self):
-        # type: () -> Tuple[Text, ...]
+    def path_parts(self) -> Tuple[Text, ...]:
         return tuple(self.path.split(os.path.sep))
 
-    def key(self):
-        # type: () -> Hashable
+    def key(self) -> Hashable:
         """A unique identifier for the test"""
         return (self.item_type, self.id)
 
-    def __eq__(self, other):
-        # type: (Any) -> bool
+    def __eq__(self, other: Any) -> bool:
         if not hasattr(other, "key"):
             return False
         return bool(self.key() == other.key())
 
-    def __hash__(self):
-        # type: () -> int
+    def __hash__(self) -> int:
         return hash(self.key())
 
-    def __repr__(self):
-        # type: () -> str
+    def __repr__(self) -> str:
         return f"<{self.__module__}.{self.__class__.__name__} id={self.id!r}, path={self.path!r}>"
 
-    def to_json(self):
-        # type: () -> Tuple[Any, ...]
+    def to_json(self) -> Tuple[Any, ...]:
         return ()
 
     @classmethod
     def from_json(cls,
-                  manifest,  # type: Manifest
-                  path,  # type: Text
-                  obj  # type: Any
-                  ):
-        # type: (...) -> ManifestItem
+                  manifest: "Manifest",
+                  path: Text,
+                  obj: Any
+                  ) -> "ManifestItem":
         path = to_os_path(path)
         tests_root = manifest.tests_root
         assert tests_root is not None
@@ -104,13 +88,12 @@
     __slots__ = ("url_base", "_url", "_extras", "_flags")
 
     def __init__(self,
-                 tests_root,  # type: Text
-                 path,  # type: Text
-                 url_base,  # type: Text
-                 url,  # type: Optional[Text]
-                 **extras  # type: Any
-                 ):
-        # type: (...) -> None
+                 tests_root: Text,
+                 path: Text,
+                 url_base: Text,
+                 url: Optional[Text],
+                 **extras: Any
+                 ) -> None:
         super().__init__(tests_root, path)
         assert url_base[0] == "/"
         self.url_base = url_base
@@ -122,13 +105,11 @@
                        set(parse_qs(parsed_url.query).get("wpt_flags", [])))
 
     @property
-    def id(self):
-        # type: () -> Text
+    def id(self) -> Text:
         return self.url
 
     @property
-    def url(self):
-        # type: () -> Text
+    def url(self) -> Text:
         rel_url = self._url or self.path.replace(os.path.sep, "/")
         # we can outperform urljoin, because we know we just have path relative URLs
         if self.url_base == "/":
@@ -136,35 +117,30 @@
         return urljoin(self.url_base, rel_url)
 
     @property
-    def https(self):
-        # type: () -> bool
+    def https(self) -> bool:
         return "https" in self._flags or "serviceworker" in self._flags or "serviceworker-module" in self._flags
 
     @property
-    def h2(self):
-        # type: () -> bool
+    def h2(self) -> bool:
         return "h2" in self._flags
 
     @property
-    def subdomain(self):
-        # type: () -> bool
+    def subdomain(self) -> bool:
         # Note: this is currently hard-coded to check for `www`, rather than
         # all possible valid subdomains. It can be extended if needed.
         return "www" in self._flags
 
-    def to_json(self):
-        # type: () -> Tuple[Optional[Text], Dict[Any, Any]]
+    def to_json(self) -> Tuple[Optional[Text], Dict[Any, Any]]:
         rel_url = None if self._url == self.path.replace(os.path.sep, "/") else self._url
-        rv = (rel_url, {})  # type: Tuple[Optional[Text], Dict[Any, Any]]
+        rv: Tuple[Optional[Text], Dict[Any, Any]] = (rel_url, {})
         return rv
 
     @classmethod
     def from_json(cls,
-                  manifest,  # type: Manifest
-                  path,  # type: Text
-                  obj  # type: Tuple[Text, Dict[Any, Any]]
-                  ):
-        # type: (...) -> URLManifestItem
+                  manifest: "Manifest",
+                  path: Text,
+                  obj: Tuple[Text, Dict[Any, Any]]
+                  ) -> "URLManifestItem":
         path = to_os_path(path)
         url, extras = obj
         tests_root = manifest.tests_root
@@ -182,32 +158,26 @@
     item_type = "testharness"
 
     @property
-    def timeout(self):
-        # type: () -> Optional[Text]
+    def timeout(self) -> Optional[Text]:
         return self._extras.get("timeout")
 
     @property
-    def pac(self):
-        # type: () -> Optional[Text]
+    def pac(self) -> Optional[Text]:
         return self._extras.get("pac")
 
     @property
-    def testdriver(self):
-        # type: () -> Optional[Text]
+    def testdriver(self) -> Optional[Text]:
         return self._extras.get("testdriver")
 
     @property
-    def jsshell(self):
-        # type: () -> Optional[Text]
+    def jsshell(self) -> Optional[Text]:
         return self._extras.get("jsshell")
 
     @property
-    def script_metadata(self):
-        # type: () -> Optional[List[Tuple[Text, Text]]]
+    def script_metadata(self) -> Optional[List[Tuple[Text, Text]]]:
         return self._extras.get("script_metadata")
 
-    def to_json(self):
-        # type: () -> Tuple[Optional[Text], Dict[Text, Any]]
+    def to_json(self) -> Tuple[Optional[Text], Dict[Text, Any]]:
         rv = super().to_json()
         if self.timeout is not None:
             rv[-1]["timeout"] = self.timeout
@@ -228,45 +198,41 @@
     item_type = "reftest"
 
     def __init__(self,
-                 tests_root,  # type: Text
-                 path,  # type: Text
-                 url_base,  # type: Text
-                 url,  # type: Optional[Text]
-                 references=None,  # type: Optional[List[Tuple[Text, Text]]]
-                 **extras  # type: Any
+                 tests_root: Text,
+                 path: Text,
+                 url_base: Text,
+                 url: Optional[Text],
+                 references: Optional[List[Tuple[Text, Text]]] = None,
+                 **extras: Any
                  ):
         super().__init__(tests_root, path, url_base, url, **extras)
         if references is None:
-            self.references = []  # type: List[Tuple[Text, Text]]
+            self.references: List[Tuple[Text, Text]] = []
         else:
             self.references = references
 
     @property
-    def timeout(self):
-        # type: () -> Optional[Text]
+    def timeout(self) -> Optional[Text]:
         return self._extras.get("timeout")
 
     @property
-    def viewport_size(self):
-        # type: () -> Optional[Text]
+    def viewport_size(self) -> Optional[Text]:
         return self._extras.get("viewport_size")
 
     @property
-    def dpi(self):
-        # type: () -> Optional[Text]
+    def dpi(self) -> Optional[Text]:
         return self._extras.get("dpi")
 
     @property
-    def fuzzy(self):
-        # type: () -> Fuzzy
-        fuzzy = self._extras.get("fuzzy", {})  # type: Union[Fuzzy, List[Tuple[Optional[Sequence[Text]], List[int]]]]
+    def fuzzy(self) -> Fuzzy:
+        fuzzy: Union[Fuzzy, List[Tuple[Optional[Sequence[Text]], List[int]]]] = self._extras.get("fuzzy", {})
         if not isinstance(fuzzy, list):
             return fuzzy
 
-        rv = {}  # type: Fuzzy
+        rv: Fuzzy = {}
         for k, v in fuzzy:  # type: Tuple[Optional[Sequence[Text]], List[int]]
             if k is None:
-                key = None  # type: Optional[Tuple[Text, Text, Text]]
+                key: Optional[Tuple[Text, Text, Text]] = None
             else:
                 # mypy types this as Tuple[Text, ...]
                 assert len(k) == 3
@@ -274,10 +240,9 @@
             rv[key] = v
         return rv
 
-    def to_json(self):  # type: ignore
-        # type: () -> Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]]
+    def to_json(self) -> Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]]:  # type: ignore
         rel_url = None if self._url == self.path else self._url
-        rv = (rel_url, self.references, {})  # type: Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]]
+        rv: Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]] = (rel_url, self.references, {})
         extras = rv[-1]
         if self.timeout is not None:
             extras["timeout"] = self.timeout
@@ -291,11 +256,10 @@
 
     @classmethod
     def from_json(cls,  # type: ignore
-                  manifest,  # type: Manifest
-                  path,  # type: Text
-                  obj  # type: Tuple[Text, List[Tuple[Text, Text]], Dict[Any, Any]]
-                  ):
-        # type: (...) -> RefTest
+                  manifest: "Manifest",
+                  path: Text,
+                  obj: Tuple[Text, List[Tuple[Text, Text]], Dict[Any, Any]]
+                  ) -> "RefTest":
         tests_root = manifest.tests_root
         assert tests_root is not None
         path = to_os_path(path)
@@ -314,8 +278,7 @@
     item_type = "print-reftest"
 
     @property
-    def page_ranges(self):
-        # type: () -> PageRanges
+    def page_ranges(self) -> PageRanges:
         return self._extras.get("page_ranges", {})
 
     def to_json(self):  # type: ignore
@@ -349,8 +312,7 @@
     item_type = "crashtest"
 
     @property
-    def timeout(self):
-        # type: () -> Optional[Text]
+    def timeout(self) -> Optional[Text]:
         return None
 
 
@@ -360,12 +322,10 @@
     item_type = "wdspec"
 
     @property
-    def timeout(self):
-        # type: () -> Optional[Text]
+    def timeout(self) -> Optional[Text]:
         return self._extras.get("timeout")
 
-    def to_json(self):
-        # type: () -> Tuple[Optional[Text], Dict[Text, Any]]
+    def to_json(self) -> Tuple[Optional[Text], Dict[Text, Any]]:
         rv = super().to_json()
         if self.timeout is not None:
             rv[-1]["timeout"] = self.timeout
@@ -378,6 +338,5 @@
     item_type = "support"
 
     @property
-    def id(self):
-        # type: () -> Text
+    def id(self) -> Text:
         return self.path
diff --git a/third_party/wpt_tools/wpt/tools/manifest/jsonlib.py b/third_party/wpt_tools/wpt/tools/manifest/jsonlib.py
index 49eaf02..9c39d5b 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/jsonlib.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/jsonlib.py
@@ -1,11 +1,6 @@
 import re
 import json
-
-
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any, AnyStr, Callable, Dict, IO, Text
+from typing import Any, AnyStr, Callable, Dict, IO, Text
 
 
 __all__ = ["load", "dump_local", "dump_local", "dump_dist", "dumps_dist"]
@@ -23,7 +18,7 @@
 #
 
 if has_ujson:
-    load = ujson.load  # type: Callable[[IO[AnyStr]], Any]
+    load: Callable[[IO[AnyStr]], Any] = ujson.load
 
 else:
     load = json.load
@@ -34,7 +29,7 @@
 #
 
 if has_ujson:
-    loads = ujson.loads  # type: Callable[[AnyStr], Any]
+    loads: Callable[[AnyStr], Any] = ujson.loads
 
 else:
     loads = json.loads
@@ -43,19 +38,19 @@
 #
 # dump/dumps_local options for some libraries
 #
-_ujson_dump_local_kwargs = {
+_ujson_dump_local_kwargs: Dict[str, Any] = {
     'ensure_ascii': False,
     'escape_forward_slashes': False,
     'indent': 1,
     'reject_bytes': True,
-}  # type: Dict[str, Any]
+}
 
 
-_json_dump_local_kwargs = {
+_json_dump_local_kwargs: Dict[str, Any] = {
     'ensure_ascii': False,
     'indent': 1,
     'separators': (',', ': '),
-}  # type: Dict[str, Any]
+}
 
 
 #
@@ -63,13 +58,11 @@
 #
 
 if has_ujson:
-    def dump_local(obj, fp):
-        # type: (Any, IO[str]) -> None
+    def dump_local(obj: Any, fp: IO[str]) -> None:
         return ujson.dump(obj, fp, **_ujson_dump_local_kwargs)
 
 else:
-    def dump_local(obj, fp):
-        # type: (Any, IO[str]) -> None
+    def dump_local(obj: Any, fp: IO[str]) -> None:
         return json.dump(obj, fp, **_json_dump_local_kwargs)
 
 
@@ -78,13 +71,11 @@
 #
 
 if has_ujson:
-    def dumps_local(obj):
-        # type: (Any) -> Text
+    def dumps_local(obj: Any) -> Text:
         return ujson.dumps(obj, **_ujson_dump_local_kwargs)
 
 else:
-    def dumps_local(obj):
-        # type: (Any) -> Text
+    def dumps_local(obj: Any) -> Text:
         return json.dumps(obj, **_json_dump_local_kwargs)
 
 
@@ -92,48 +83,42 @@
 # dump/dumps_dist (for distributed usage of JSON where files should safely roundtrip)
 #
 
-_ujson_dump_dist_kwargs = {
+_ujson_dump_dist_kwargs: Dict[str, Any] = {
     'sort_keys': True,
     'indent': 1,
     'reject_bytes': True,
-}  # type: Dict[str, Any]
+}
 
 
-_json_dump_dist_kwargs = {
+_json_dump_dist_kwargs: Dict[str, Any] = {
     'sort_keys': True,
     'indent': 1,
     'separators': (',', ': '),
-}  # type: Dict[str, Any]
+}
 
 
 if has_ujson:
     if ujson.dumps([], indent=1) == "[]":
         # optimistically see if https://github.com/ultrajson/ultrajson/issues/429 is fixed
-        def _ujson_fixup(s):
-            # type: (str) -> str
+        def _ujson_fixup(s: str) -> str:
             return s
     else:
         _ujson_fixup_re = re.compile(r"([\[{])[\n\x20]+([}\]])")
 
-        def _ujson_fixup(s):
-            # type: (str) -> str
+        def _ujson_fixup(s: str) -> str:
             return _ujson_fixup_re.sub(
                 lambda m: m.group(1) + m.group(2),
                 s
             )
 
-    def dump_dist(obj, fp):
-        # type: (Any, IO[str]) -> None
+    def dump_dist(obj: Any, fp: IO[str]) -> None:
         fp.write(_ujson_fixup(ujson.dumps(obj, **_ujson_dump_dist_kwargs)))
 
-    def dumps_dist(obj):
-        # type: (Any) -> Text
+    def dumps_dist(obj: Any) -> Text:
         return _ujson_fixup(ujson.dumps(obj, **_ujson_dump_dist_kwargs))
 else:
-    def dump_dist(obj, fp):
-        # type: (Any, IO[str]) -> None
+    def dump_dist(obj: Any, fp: IO[str]) -> None:
         json.dump(obj, fp, **_json_dump_dist_kwargs)
 
-    def dumps_dist(obj):
-        # type: (Any) -> Text
+    def dumps_dist(obj: Any) -> Text:
         return json.dumps(obj, **_json_dump_dist_kwargs)
diff --git a/third_party/wpt_tools/wpt/tools/manifest/log.py b/third_party/wpt_tools/wpt/tools/manifest/log.py
index 6551c2b..788138173 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/log.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/log.py
@@ -2,10 +2,8 @@
 
 logger = logging.getLogger("manifest")
 
-def enable_debug_logging():
-    # type: () -> None
+def enable_debug_logging() -> None:
     logger.setLevel(logging.DEBUG)
 
-def get_logger():
-    # type: () -> logging.Logger
+def get_logger() -> logging.Logger:
     return logger
diff --git a/third_party/wpt_tools/wpt/tools/manifest/manifest.py b/third_party/wpt_tools/wpt/tools/manifest/manifest.py
index 4b7792e..f602727 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/manifest.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/manifest.py
@@ -2,7 +2,10 @@
 import sys
 from atomicwrites import atomic_write
 from copy import deepcopy
+from logging import Logger
 from multiprocessing import Pool, cpu_count
+from typing import (Any, Container, Dict, IO, Iterator, Iterable, Optional, Set, Text, Tuple, Type,
+                    Union)
 
 from . import jsonlib
 from . import vcs
@@ -20,25 +23,9 @@
 from .sourcefile import SourceFile
 from .typedata import TypeData
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from logging import Logger
-    from typing import Any
-    from typing import Container
-    from typing import Dict
-    from typing import IO
-    from typing import Iterator
-    from typing import Iterable
-    from typing import Optional
-    from typing import Set
-    from typing import Text
-    from typing import Tuple
-    from typing import Type
-    from typing import Union
 
 
-CURRENT_VERSION = 8  # type: int
+CURRENT_VERSION: int = 8
 
 
 class ManifestError(Exception):
@@ -53,60 +40,52 @@
     pass
 
 
-item_classes = {"testharness": TestharnessTest,
-                "reftest": RefTest,
-                "print-reftest": PrintRefTest,
-                "crashtest": CrashTest,
-                "manual": ManualTest,
-                "wdspec": WebDriverSpecTest,
-                "conformancechecker": ConformanceCheckerTest,
-                "visual": VisualTest,
-                "support": SupportFile}  # type: Dict[Text, Type[ManifestItem]]
+item_classes: Dict[Text, Type[ManifestItem]] = {"testharness": TestharnessTest,
+                                                "reftest": RefTest,
+                                                "print-reftest": PrintRefTest,
+                                                "crashtest": CrashTest,
+                                                "manual": ManualTest,
+                                                "wdspec": WebDriverSpecTest,
+                                                "conformancechecker": ConformanceCheckerTest,
+                                                "visual": VisualTest,
+                                                "support": SupportFile}
 
 
-def compute_manifest_items(source_file):
-    # type: (SourceFile) -> Tuple[Tuple[Text, ...], Text, Set[ManifestItem], Text]
+def compute_manifest_items(source_file: SourceFile) -> Tuple[Tuple[Text, ...], Text, Set[ManifestItem], Text]:
     rel_path_parts = source_file.rel_path_parts
     new_type, manifest_items = source_file.manifest_items()
     file_hash = source_file.hash
     return rel_path_parts, new_type, set(manifest_items), file_hash
 
 
-if MYPY:
-    ManifestDataType = Dict[Any, TypeData]
-else:
-    ManifestDataType = dict
+ManifestDataType = Dict[Any, TypeData]
 
 
 class ManifestData(ManifestDataType):
-    def __init__(self, manifest):
-        # type: (Manifest) -> None
+    def __init__(self, manifest: "Manifest") -> None:
         """Dictionary subclass containing a TypeData instance for each test type,
         keyed by type name"""
-        self.initialized = False  # type: bool
+        self.initialized: bool = False
         for key, value in item_classes.items():
             self[key] = TypeData(manifest, value)
         self.initialized = True
-        self.json_obj = None  # type: None
+        self.json_obj: None = None
 
-    def __setitem__(self, key, value):
-        # type: (Text, TypeData) -> None
+    def __setitem__(self, key: Text, value: TypeData) -> None:
         if self.initialized:
             raise AttributeError
         dict.__setitem__(self, key, value)
 
-    def paths(self):
-        # type: () -> Set[Text]
+    def paths(self) -> Set[Text]:
         """Get a list of all paths containing test items
         without actually constructing all the items"""
-        rv = set()  # type: Set[Text]
+        rv: Set[Text] = set()
         for item_data in self.values():
             for item in item_data:
                 rv.add(os.path.sep.join(item))
         return rv
 
-    def type_by_path(self):
-        # type: () -> Dict[Tuple[Text, ...], Text]
+    def type_by_path(self) -> Dict[Tuple[Text, ...], Text]:
         rv = {}
         for item_type, item_data in self.items():
             for item in item_data:
@@ -115,27 +94,23 @@
 
 
 class Manifest:
-    def __init__(self, tests_root, url_base="/"):
-        # type: (Text, Text) -> None
+    def __init__(self, tests_root: Text, url_base: Text = "/") -> None:
         assert url_base is not None
-        self._data = ManifestData(self)  # type: ManifestData
-        self.tests_root = tests_root  # type: Text
-        self.url_base = url_base  # type: Text
+        self._data: ManifestData = ManifestData(self)
+        self.tests_root: Text = tests_root
+        self.url_base: Text = url_base
 
-    def __iter__(self):
-        # type: () -> Iterator[Tuple[Text, Text, Set[ManifestItem]]]
+    def __iter__(self) -> Iterator[Tuple[Text, Text, Set[ManifestItem]]]:
         return self.itertypes()
 
-    def itertypes(self, *types):
-        # type: (*Text) -> Iterator[Tuple[Text, Text, Set[ManifestItem]]]
+    def itertypes(self, *types: Text) -> Iterator[Tuple[Text, Text, Set[ManifestItem]]]:
         for item_type in (types or sorted(self._data.keys())):
             for path in self._data[item_type]:
                 rel_path = os.sep.join(path)
                 tests = self._data[item_type][path]
                 yield item_type, rel_path, tests
 
-    def iterpath(self, path):
-        # type: (Text) -> Iterable[ManifestItem]
+    def iterpath(self, path: Text) -> Iterable[ManifestItem]:
         tpath = tuple(path.split(os.path.sep))
 
         for type_tests in self._data.values():
@@ -143,8 +118,7 @@
             assert i is not None
             yield from i
 
-    def iterdir(self, dir_name):
-        # type: (Text) -> Iterable[ManifestItem]
+    def iterdir(self, dir_name: Text) -> Iterable[ManifestItem]:
         tpath = tuple(dir_name.split(os.path.sep))
         tpath_len = len(tpath)
 
@@ -153,8 +127,7 @@
                 if path[:tpath_len] == tpath:
                     yield from tests
 
-    def update(self, tree, parallel=True):
-        # type: (Iterable[Tuple[Text, Optional[Text], bool]], bool) -> bool
+    def update(self, tree: Iterable[Tuple[Text, Optional[Text], bool]], parallel: bool = True) -> bool:
         """Update the manifest given an iterable of items that make up the updated manifest.
 
         The iterable must either generate tuples of the form (SourceFile, True) for paths
@@ -197,7 +170,7 @@
                                          self.url_base,
                                          file_hash)
 
-                hash_changed = False  # type: bool
+                hash_changed: bool = False
 
                 if not is_new:
                     if file_hash is None:
@@ -238,10 +211,12 @@
             chunksize = max(1, len(to_update) // 10000)
             logger.debug("Doing a multiprocessed update. CPU count: %s, "
                 "processes: %s, chunksize: %s" % (cpu_count(), processes, chunksize))
-            results = pool.imap_unordered(compute_manifest_items,
-                                          to_update,
-                                          chunksize=chunksize
-                                          )  # type: Iterator[Tuple[Tuple[Text, ...], Text, Set[ManifestItem], Text]]
+            results: Iterator[Tuple[Tuple[Text, ...],
+                                    Text,
+                                    Set[ManifestItem], Text]] = pool.imap_unordered(
+                                        compute_manifest_items,
+                                        to_update,
+                                        chunksize=chunksize)
         else:
             results = map(compute_manifest_items, to_update)
 
@@ -264,8 +239,7 @@
 
         return changed
 
-    def to_json(self, caller_owns_obj=True):
-        # type: (bool) -> Dict[Text, Any]
+    def to_json(self, caller_owns_obj: bool = True) -> Dict[Text, Any]:
         """Dump a manifest into a object which can be serialized as JSON
 
         If caller_owns_obj is False, then the return value remains
@@ -284,14 +258,17 @@
         if caller_owns_obj:
             out_items = deepcopy(out_items)
 
-        rv = {"url_base": self.url_base,
+        rv: Dict[Text, Any] = {"url_base": self.url_base,
               "items": out_items,
-              "version": CURRENT_VERSION}  # type: Dict[Text, Any]
+              "version": CURRENT_VERSION}
         return rv
 
     @classmethod
-    def from_json(cls, tests_root, obj, types=None, callee_owns_obj=False):
-        # type: (Text, Dict[Text, Any], Optional[Container[Text]], bool) -> Manifest
+    def from_json(cls,
+                  tests_root: Text,
+                  obj: Dict[Text, Any],
+                  types: Optional[Container[Text]] = None,
+                  callee_owns_obj: bool = False) -> "Manifest":
         """Load a manifest from a JSON object
 
         This loads a manifest for a given local test_root path from an
@@ -327,24 +304,22 @@
         return self
 
 
-def load(tests_root, manifest, types=None):
-    # type: (Text, Union[IO[bytes], Text], Optional[Container[Text]]) -> Optional[Manifest]
+def load(tests_root: Text, manifest: Union[IO[bytes], Text], types: Optional[Container[Text]] = None) -> Optional[Manifest]:
     logger = get_logger()
 
     logger.warning("Prefer load_and_update instead")
     return _load(logger, tests_root, manifest, types)
 
 
-__load_cache = {}  # type: Dict[Text, Manifest]
+__load_cache: Dict[Text, Manifest] = {}
 
 
-def _load(logger,  # type: Logger
-          tests_root,  # type: Text
-          manifest,  # type: Union[IO[bytes], Text]
-          types=None,  # type: Optional[Container[Text]]
-          allow_cached=True  # type: bool
-          ):
-    # type: (...) -> Optional[Manifest]
+def _load(logger: Logger,
+          tests_root: Text,
+          manifest: Union[IO[bytes], Text],
+          types: Optional[Container[Text]] = None,
+          allow_cached: bool = True
+          ) -> Optional[Manifest]:
     manifest_path = (manifest if isinstance(manifest, str)
                      else manifest.name)
     if allow_cached and manifest_path in __load_cache:
@@ -377,20 +352,19 @@
     return rv
 
 
-def load_and_update(tests_root,  # type: Text
-                    manifest_path,  # type: Text
-                    url_base,  # type: Text
-                    update=True,  # type: bool
-                    rebuild=False,  # type: bool
-                    metadata_path=None,  # type: Optional[Text]
-                    cache_root=None,  # type: Optional[Text]
-                    working_copy=True,  # type: bool
-                    types=None,  # type: Optional[Container[Text]]
-                    write_manifest=True,  # type: bool
-                    allow_cached=True,  # type: bool
-                    parallel=True  # type: bool
-                    ):
-    # type: (...) -> Manifest
+def load_and_update(tests_root: Text,
+                    manifest_path: Text,
+                    url_base: Text,
+                    update: bool = True,
+                    rebuild: bool = False,
+                    metadata_path: Optional[Text] = None,
+                    cache_root: Optional[Text] = None,
+                    working_copy: bool = True,
+                    types: Optional[Container[Text]] = None,
+                    write_manifest: bool = True,
+                    allow_cached: bool = True,
+                    parallel: bool = True
+                    ) -> Manifest:
 
     logger = get_logger()
 
@@ -437,8 +411,7 @@
     return manifest
 
 
-def write(manifest, manifest_path):
-    # type: (Manifest, Text) -> None
+def write(manifest: Manifest, manifest_path: Text) -> None:
     dir_name = os.path.dirname(manifest_path)
     if not os.path.exists(dir_name):
         os.makedirs(dir_name)
diff --git a/third_party/wpt_tools/wpt/tools/manifest/sourcefile.py b/third_party/wpt_tools/wpt/tools/manifest/sourcefile.py
index 3919b5a..3868337 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/sourcefile.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/sourcefile.py
@@ -2,27 +2,11 @@
 import re
 import os
 from collections import deque
-from io import BytesIO
-from urllib.parse import urljoin
 from fnmatch import fnmatch
-
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import BinaryIO
-    from typing import Callable
-    from typing import Deque
-    from typing import Dict
-    from typing import Iterable
-    from typing import List
-    from typing import Optional
-    from typing import Pattern
-    from typing import Set
-    from typing import Text
-    from typing import Tuple
-    from typing import Union
-    from typing import cast
+from io import BytesIO
+from typing import (Any, BinaryIO, Callable, Deque, Dict, Iterable, List, Optional, Pattern,
+                    Set, Text, Tuple, Union, cast)
+from urllib.parse import urljoin
 
 try:
     from xml.etree import cElementTree as ElementTree
@@ -50,11 +34,10 @@
 
 reference_file_re = re.compile(r'(^|[\-_])(not)?ref[0-9]*([\-_]|$)')
 
-space_chars = "".join(html5lib.constants.spaceCharacters)  # type: Text
+space_chars: Text = "".join(html5lib.constants.spaceCharacters)
 
 
-def replace_end(s, old, new):
-    # type: (Text, Text, Text) -> Text
+def replace_end(s: Text, old: Text, new: Text) -> Text:
     """
     Given a string `s` that ends with `old`, replace that occurrence of `old`
     with `new`.
@@ -63,8 +46,7 @@
     return s[:-len(old)] + new
 
 
-def read_script_metadata(f, regexp):
-    # type: (BinaryIO, Pattern[bytes]) -> Iterable[Tuple[Text, Text]]
+def read_script_metadata(f: BinaryIO, regexp: Pattern[bytes]) -> Iterable[Tuple[Text, Text]]:
     """
     Yields any metadata (pairs of strings) from the file-like object `f`,
     as specified according to a supplied regexp.
@@ -81,7 +63,7 @@
         yield (m.groups()[0].decode("utf8"), m.groups()[1].decode("utf8"))
 
 
-_any_variants = {
+_any_variants: Dict[Text, Dict[Text, Any]] = {
     "window": {"suffix": ".any.html"},
     "serviceworker": {"force_https": True},
     "serviceworker-module": {"force_https": True},
@@ -93,11 +75,10 @@
     "worker-module": {},
     "shadowrealm": {},
     "jsshell": {"suffix": ".any.js"},
-}  # type: Dict[Text, Dict[Text, Any]]
+}
 
 
-def get_any_variants(item):
-    # type: (Text) -> Set[Text]
+def get_any_variants(item: Text) -> Set[Text]:
     """
     Returns a set of variants (strings) defined by the given keyword.
     """
@@ -110,16 +91,14 @@
     return variant.get("longhand", {item})
 
 
-def get_default_any_variants():
-    # type: () -> Set[Text]
+def get_default_any_variants() -> Set[Text]:
     """
     Returns a set of variants (strings) that will be used by default.
     """
     return set({"window", "dedicatedworker"})
 
 
-def parse_variants(value):
-    # type: (Text) -> Set[Text]
+def parse_variants(value: Text) -> Set[Text]:
     """
     Returns a set of variants (strings) defined by a comma-separated value.
     """
@@ -135,8 +114,7 @@
     return globals
 
 
-def global_suffixes(value):
-    # type: (Text) -> Set[Tuple[Text, bool]]
+def global_suffixes(value: Text) -> Set[Tuple[Text, bool]]:
     """
     Yields tuples of the relevant filename suffix (a string) and whether the
     variant is intended to run in a JS shell, for the variants defined by the
@@ -155,8 +133,7 @@
     return rv
 
 
-def global_variant_url(url, suffix):
-    # type: (Text, Text) -> Text
+def global_variant_url(url: Text, suffix: Text) -> Text:
     """
     Returns a url created from the given url and suffix (all strings).
     """
@@ -170,17 +147,11 @@
     return replace_end(url, ".js", suffix)
 
 
-def _parse_html(f):
-    # type: (BinaryIO) -> ElementTree.Element
+def _parse_html(f: BinaryIO) -> ElementTree.Element:
     doc = html5lib.parse(f, treebuilder="etree", useChardet=False)
-    if MYPY:
-        return cast(ElementTree.Element, doc)
-    else:
-        # (needs to be in else for mypy to believe this is reachable)
-        return doc
+    return cast(ElementTree.Element, doc)
 
-def _parse_xml(f):
-    # type: (BinaryIO) -> ElementTree.Element
+def _parse_xml(f: BinaryIO) -> ElementTree.Element:
     try:
         # raises ValueError with an unsupported encoding,
         # ParseError when there's an undefined entity
@@ -191,9 +162,9 @@
 
 
 class SourceFile:
-    parsers = {"html":_parse_html,
+    parsers: Dict[Text, Callable[[BinaryIO], ElementTree.Element]] = {"html":_parse_html,
                "xhtml":_parse_xml,
-               "svg":_parse_xml}  # type: Dict[Text, Callable[[BinaryIO], ElementTree.Element]]
+               "svg":_parse_xml}
 
     root_dir_non_test = {"common"}
 
@@ -201,12 +172,15 @@
                     "support",
                     "tools"}
 
-    dir_path_non_test = {("css21", "archive"),
-                         ("css", "CSS2", "archive"),
-                         ("css", "common")}  # type: Set[Tuple[Text, ...]]
+    dir_path_non_test: Set[Tuple[Text, ...]] = {("css21", "archive"),
+                                                ("css", "CSS2", "archive"),
+                                                ("css", "common")}
 
-    def __init__(self, tests_root, rel_path, url_base, hash=None, contents=None):
-        # type: (Text, Text, Text, Optional[Text], Optional[bytes]) -> None
+    def __init__(self, tests_root: Text,
+                 rel_path: Text,
+                 url_base: Text,
+                 hash: Optional[Text] = None,
+                 contents: Optional[bytes] = None) -> None:
         """Object representing a file in a source tree.
 
         :param tests_root: Path to the root of the source tree
@@ -229,21 +203,20 @@
 
         meta_flags = name.split(".")[1:]
 
-        self.tests_root = tests_root  # type: Text
-        self.rel_path = rel_path  # type: Text
-        self.dir_path = dir_path  # type: Text
-        self.filename = filename  # type: Text
-        self.name = name  # type: Text
-        self.ext = ext  # type: Text
-        self.type_flag = type_flag  # type: Optional[Text]
-        self.meta_flags = meta_flags  # type: Union[List[bytes], List[Text]]
+        self.tests_root: Text = tests_root
+        self.rel_path: Text = rel_path
+        self.dir_path: Text = dir_path
+        self.filename: Text = filename
+        self.name: Text = name
+        self.ext: Text = ext
+        self.type_flag: Optional[Text] = type_flag
+        self.meta_flags: Union[List[bytes], List[Text]] = meta_flags
         self.url_base = url_base
         self.contents = contents
-        self.items_cache = None  # type: Optional[Tuple[Text, List[ManifestItem]]]
+        self.items_cache: Optional[Tuple[Text, List[ManifestItem]]] = None
         self._hash = hash
 
-    def __getstate__(self):
-        # type: () -> Dict[str, Any]
+    def __getstate__(self) -> Dict[str, Any]:
         # Remove computed properties if we pickle this class
         rv = self.__dict__.copy()
 
@@ -253,58 +226,50 @@
             del rv["__cached_properties__"]
         return rv
 
-    def name_prefix(self, prefix):
-        # type: (Text) -> bool
+    def name_prefix(self, prefix: Text) -> bool:
         """Check if the filename starts with a given prefix
 
         :param prefix: The prefix to check"""
         return self.name.startswith(prefix)
 
-    def is_dir(self):
-        # type: () -> bool
+    def is_dir(self) -> bool:
         """Return whether this file represents a directory."""
         if self.contents is not None:
             return False
 
         return os.path.isdir(self.rel_path)
 
-    def open(self):
-        # type: () -> BinaryIO
+    def open(self) -> BinaryIO:
         """
         Return either
         * the contents specified in the constructor, if any;
         * a File object opened for reading the file contents.
         """
         if self.contents is not None:
-            file_obj = BytesIO(self.contents)  # type: BinaryIO
+            file_obj: BinaryIO = BytesIO(self.contents)
         else:
             file_obj = open(self.path, 'rb')
         return file_obj
 
     @cached_property
-    def rel_path_parts(self):
-        # type: () -> Tuple[Text, ...]
+    def rel_path_parts(self) -> Tuple[Text, ...]:
         return tuple(self.rel_path.split(os.path.sep))
 
     @cached_property
-    def path(self):
-        # type: () -> Text
+    def path(self) -> Text:
         return os.path.join(self.tests_root, self.rel_path)
 
     @cached_property
-    def rel_url(self):
-        # type: () -> Text
+    def rel_url(self) -> Text:
         assert not os.path.isabs(self.rel_path), self.rel_path
         return self.rel_path.replace(os.sep, "/")
 
     @cached_property
-    def url(self):
-        # type: () -> Text
+    def url(self) -> Text:
         return urljoin(self.url_base, self.rel_url)
 
     @cached_property
-    def hash(self):
-        # type: () -> Text
+    def hash(self) -> Text:
         if not self._hash:
             with self.open() as f:
                 content = f.read()
@@ -314,8 +279,7 @@
 
         return self._hash
 
-    def in_non_test_dir(self):
-        # type: () -> bool
+    def in_non_test_dir(self) -> bool:
         if self.dir_path == "":
             return True
 
@@ -327,13 +291,11 @@
             return True
         return False
 
-    def in_conformance_checker_dir(self):
-        # type: () -> bool
+    def in_conformance_checker_dir(self) -> bool:
         return self.rel_path_parts[0] == "conformance-checkers"
 
     @property
-    def name_is_non_test(self):
-        # type: () -> bool
+    def name_is_non_test(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a non-test file"""
         return (self.is_dir() or
@@ -345,54 +307,46 @@
                 self.in_non_test_dir())
 
     @property
-    def name_is_conformance(self):
-        # type: () -> bool
+    def name_is_conformance(self) -> bool:
         return (self.in_conformance_checker_dir() and
                 self.type_flag in ("is-valid", "no-valid"))
 
     @property
-    def name_is_conformance_support(self):
-        # type: () -> bool
+    def name_is_conformance_support(self) -> bool:
         return self.in_conformance_checker_dir()
 
     @property
-    def name_is_manual(self):
-        # type: () -> bool
+    def name_is_manual(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a manual test file"""
         return self.type_flag == "manual"
 
     @property
-    def name_is_visual(self):
-        # type: () -> bool
+    def name_is_visual(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a visual test file"""
         return self.type_flag == "visual"
 
     @property
-    def name_is_multi_global(self):
-        # type: () -> bool
+    def name_is_multi_global(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a multi-global js test file"""
         return "any" in self.meta_flags and self.ext == ".js"
 
     @property
-    def name_is_worker(self):
-        # type: () -> bool
+    def name_is_worker(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a worker js test file"""
         return "worker" in self.meta_flags and self.ext == ".js"
 
     @property
-    def name_is_window(self):
-        # type: () -> bool
+    def name_is_window(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a window js test file"""
         return "window" in self.meta_flags and self.ext == ".js"
 
     @property
-    def name_is_webdriver(self):
-        # type: () -> bool
+    def name_is_webdriver(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a webdriver spec test file"""
         # wdspec tests are in subdirectories of /webdriver excluding __init__.py
@@ -405,21 +359,18 @@
                 fnmatch(self.filename, wd_pattern))
 
     @property
-    def name_is_reference(self):
-        # type: () -> bool
+    def name_is_reference(self) -> bool:
         """Check if the file name matches the conditions for the file to
         be a reference file (not a reftest)"""
         return "/reference/" in self.url or bool(reference_file_re.search(self.name))
 
     @property
-    def name_is_crashtest(self):
-        # type: () -> bool
+    def name_is_crashtest(self) -> bool:
         return (self.markup_type is not None and
                 (self.type_flag == "crash" or "crashtests" in self.dir_path.split(os.path.sep)))
 
     @property
-    def name_is_tentative(self):
-        # type: () -> bool
+    def name_is_tentative(self) -> bool:
         """Check if the file name matches the conditions for the file to be a
         tentative file.
 
@@ -427,14 +378,12 @@
         return "tentative" in self.meta_flags or "tentative" in self.dir_path.split(os.path.sep)
 
     @property
-    def name_is_print_reftest(self):
-        # type: () -> bool
+    def name_is_print_reftest(self) -> bool:
         return (self.markup_type is not None and
                 (self.type_flag == "print" or "print" in self.dir_path.split(os.path.sep)))
 
     @property
-    def markup_type(self):
-        # type: () -> Optional[Text]
+    def markup_type(self) -> Optional[Text]:
         """Return the type of markup contained in a file, based on its extension,
         or None if it doesn't contain markup"""
         ext = self.ext
@@ -452,8 +401,7 @@
         return None
 
     @cached_property
-    def root(self):
-        # type: () -> Optional[ElementTree.Element]
+    def root(self) -> Optional[ElementTree.Element]:
         """Return an ElementTree Element for the root node of the file if it contains
         markup, or None if it does not"""
         if not self.markup_type:
@@ -470,24 +418,21 @@
         return tree
 
     @cached_property
-    def timeout_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def timeout_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify timeouts"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='timeout']")
 
     @cached_property
-    def pac_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def pac_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify PAC (proxy auto-config)"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='pac']")
 
     @cached_property
-    def script_metadata(self):
-        # type: () -> Optional[List[Tuple[Text, Text]]]
+    def script_metadata(self) -> Optional[List[Tuple[Text, Text]]]:
         if self.name_is_worker or self.name_is_multi_global or self.name_is_window:
             regexp = js_meta_re
         elif self.name_is_webdriver:
@@ -499,8 +444,7 @@
             return list(read_script_metadata(f, regexp))
 
     @cached_property
-    def timeout(self):
-        # type: () -> Optional[Text]
+    def timeout(self) -> Optional[Text]:
         """The timeout of a test or reference file. "long" if the file has an extended timeout
         or None otherwise"""
         if self.script_metadata:
@@ -511,15 +455,14 @@
             return None
 
         if self.timeout_nodes:
-            timeout_str = self.timeout_nodes[0].attrib.get("content", None)  # type: Optional[Text]
+            timeout_str: Optional[Text] = self.timeout_nodes[0].attrib.get("content", None)
             if timeout_str and timeout_str.lower() == "long":
                 return "long"
 
         return None
 
     @cached_property
-    def pac(self):
-        # type: () -> Optional[Text]
+    def pac(self) -> Optional[Text]:
         """The PAC (proxy config) of a test or reference file. A URL or null"""
         if self.script_metadata:
             for (meta, content) in self.script_metadata:
@@ -535,16 +478,14 @@
         return None
 
     @cached_property
-    def viewport_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def viewport_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify viewport sizes"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='viewport-size']")
 
     @cached_property
-    def viewport_size(self):
-        # type: () -> Optional[Text]
+    def viewport_size(self) -> Optional[Text]:
         """The viewport size of a test or reference file"""
         if self.root is None:
             return None
@@ -555,16 +496,14 @@
         return self.viewport_nodes[0].attrib.get("content", None)
 
     @cached_property
-    def dpi_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def dpi_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify device pixel ratios"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='device-pixel-ratio']")
 
     @cached_property
-    def dpi(self):
-        # type: () -> Optional[Text]
+    def dpi(self) -> Optional[Text]:
         """The device pixel ratio of a test or reference file"""
         if self.root is None:
             return None
@@ -574,13 +513,12 @@
 
         return self.dpi_nodes[0].attrib.get("content", None)
 
-    def parse_ref_keyed_meta(self, node):
-        # type: (ElementTree.Element) -> Tuple[Optional[Tuple[Text, Text, Text]], Text]
-        item = node.attrib.get("content", "")  # type: Text
+    def parse_ref_keyed_meta(self, node: ElementTree.Element) -> Tuple[Optional[Tuple[Text, Text, Text]], Text]:
+        item: Text = node.attrib.get("content", "")
 
         parts = item.rsplit(":", 1)
         if len(parts) == 1:
-            key = None  # type: Optional[Tuple[Text, Text, Text]]
+            key: Optional[Tuple[Text, Text, Text]] = None
             value = parts[0]
         else:
             key_part = urljoin(self.url, parts[0])
@@ -598,8 +536,7 @@
 
 
     @cached_property
-    def fuzzy_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def fuzzy_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify reftest fuzziness"""
         assert self.root is not None
@@ -607,9 +544,8 @@
 
 
     @cached_property
-    def fuzzy(self):
-        # type: () -> Dict[Optional[Tuple[Text, Text, Text]], List[List[int]]]
-        rv = {}  # type: Dict[Optional[Tuple[Text, Text, Text]], List[List[int]]]
+    def fuzzy(self) -> Dict[Optional[Tuple[Text, Text, Text]], List[List[int]]]:
+        rv: Dict[Optional[Tuple[Text, Text, Text]], List[List[int]]] = {}
         if self.root is None:
             return rv
 
@@ -623,10 +559,10 @@
             ranges = value.split(";")
             if len(ranges) != 2:
                 raise ValueError("Malformed fuzzy value %s" % value)
-            arg_values = {}  # type: Dict[Text, List[int]]
-            positional_args = deque()  # type: Deque[List[int]]
+            arg_values: Dict[Text, List[int]] = {}
+            positional_args: Deque[List[int]] = deque()
             for range_str_value in ranges:  # type: Text
-                name = None  # type: Optional[Text]
+                name: Optional[Text] = None
                 if "=" in range_str_value:
                     name, range_str_value = (part.strip()
                                              for part in range_str_value.split("=", 1))
@@ -659,19 +595,17 @@
         return rv
 
     @cached_property
-    def page_ranges_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def page_ranges_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify print-reftest """
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='reftest-pages']")
 
     @cached_property
-    def page_ranges(self):
-        # type: () -> Dict[Text, List[List[Optional[int]]]]
+    def page_ranges(self) -> Dict[Text, List[List[Optional[int]]]]:
         """List of ElementTree Elements corresponding to nodes in a test that
         specify print-reftest page ranges"""
-        rv = {}  # type: Dict[Text, List[List[Optional[int]]]]
+        rv: Dict[Text, List[List[Optional[int]]]] = {}
         for node in self.page_ranges_nodes:
             key_data, value = self.parse_ref_keyed_meta(node)
             # Just key by url
@@ -701,16 +635,14 @@
         return rv
 
     @cached_property
-    def testharness_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def testharness_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes representing a
         testharness.js script"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src='/resources/testharness.js']")
 
     @cached_property
-    def content_is_testharness(self):
-        # type: () -> Optional[bool]
+    def content_is_testharness(self) -> Optional[bool]:
         """Boolean indicating whether the file content represents a
         testharness.js test"""
         if self.root is None:
@@ -718,17 +650,15 @@
         return bool(self.testharness_nodes)
 
     @cached_property
-    def variant_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def variant_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes representing a
         test variant"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='variant']")
 
     @cached_property
-    def test_variants(self):
-        # type: () -> List[Text]
-        rv = []  # type: List[Text]
+    def test_variants(self) -> List[Text]:
+        rv: List[Text] = []
         if self.ext == ".js":
             script_metadata = self.script_metadata
             assert script_metadata is not None
@@ -738,7 +668,7 @@
         else:
             for element in self.variant_nodes:
                 if "content" in element.attrib:
-                    variant = element.attrib["content"]  # type: Text
+                    variant: Text = element.attrib["content"]
                     rv.append(variant)
 
         for variant in rv:
@@ -755,16 +685,14 @@
         return rv
 
     @cached_property
-    def testdriver_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def testdriver_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes representing a
         testdriver.js script"""
         assert self.root is not None
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src='/resources/testdriver.js']")
 
     @cached_property
-    def has_testdriver(self):
-        # type: () -> Optional[bool]
+    def has_testdriver(self) -> Optional[bool]:
         """Boolean indicating whether the file content represents a
         testharness.js test"""
         if self.root is None:
@@ -772,8 +700,7 @@
         return bool(self.testdriver_nodes)
 
     @cached_property
-    def reftest_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def reftest_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes representing a
         to a reftest <link>"""
         if self.root is None:
@@ -784,11 +711,10 @@
         return match_links + mismatch_links
 
     @cached_property
-    def references(self):
-        # type: () -> List[Tuple[Text, Text]]
+    def references(self) -> List[Tuple[Text, Text]]:
         """List of (ref_url, relation) tuples for any reftest references specified in
         the file"""
-        rv = []  # type: List[Tuple[Text, Text]]
+        rv: List[Tuple[Text, Text]] = []
         rel_map = {"match": "==", "mismatch": "!="}
         for item in self.reftest_nodes:
             if "href" in item.attrib:
@@ -798,15 +724,13 @@
         return rv
 
     @cached_property
-    def content_is_ref_node(self):
-        # type: () -> bool
+    def content_is_ref_node(self) -> bool:
         """Boolean indicating whether the file is a non-leaf node in a reftest
         graph (i.e. if it contains any <link rel=[mis]match>"""
         return bool(self.references)
 
     @cached_property
-    def css_flag_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def css_flag_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes representing a
         flag <meta>"""
         if self.root is None:
@@ -814,10 +738,9 @@
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='flags']")
 
     @cached_property
-    def css_flags(self):
-        # type: () -> Set[Text]
+    def css_flags(self) -> Set[Text]:
         """Set of flags specified in the file"""
-        rv = set()  # type: Set[Text]
+        rv: Set[Text] = set()
         for item in self.css_flag_nodes:
             if "content" in item.attrib:
                 for flag in item.attrib["content"].split():
@@ -825,8 +748,7 @@
         return rv
 
     @cached_property
-    def content_is_css_manual(self):
-        # type: () -> Optional[bool]
+    def content_is_css_manual(self) -> Optional[bool]:
         """Boolean indicating whether the file content represents a
         CSS WG-style manual test"""
         if self.root is None:
@@ -835,8 +757,7 @@
         return bool(self.css_flags & {"animated", "font", "history", "interact", "paged", "speech", "userstyle"})
 
     @cached_property
-    def spec_link_nodes(self):
-        # type: () -> List[ElementTree.Element]
+    def spec_link_nodes(self) -> List[ElementTree.Element]:
         """List of ElementTree Elements corresponding to nodes representing a
         <link rel=help>, used to point to specs"""
         if self.root is None:
@@ -844,18 +765,16 @@
         return self.root.findall(".//{http://www.w3.org/1999/xhtml}link[@rel='help']")
 
     @cached_property
-    def spec_links(self):
-        # type: () -> Set[Text]
+    def spec_links(self) -> Set[Text]:
         """Set of spec links specified in the file"""
-        rv = set()  # type: Set[Text]
+        rv: Set[Text] = set()
         for item in self.spec_link_nodes:
             if "href" in item.attrib:
                 rv.add(item.attrib["href"].strip(space_chars))
         return rv
 
     @cached_property
-    def content_is_css_visual(self):
-        # type: () -> Optional[bool]
+    def content_is_css_visual(self) -> Optional[bool]:
         """Boolean indicating whether the file content represents a
         CSS WG-style visual test"""
         if self.root is None:
@@ -864,8 +783,7 @@
                     self.spec_links)
 
     @property
-    def type(self):
-        # type: () -> Text
+    def type(self) -> Text:
         possible_types = self.possible_types
         if len(possible_types) == 1:
             return possible_types.pop()
@@ -874,8 +792,7 @@
         return rv
 
     @property
-    def possible_types(self):
-        # type: () -> Set[Text]
+    def possible_types(self) -> Set[Text]:
         """Determines the set of possible types without reading the file"""
 
         if self.items_cache:
@@ -928,8 +845,7 @@
                 RefTest.item_type,
                 SupportFile.item_type}
 
-    def manifest_items(self):
-        # type: () -> Tuple[Text, List[ManifestItem]]
+    def manifest_items(self) -> Tuple[Text, List[ManifestItem]]:
         """List of manifest items corresponding to the file. There is typically one
         per test, but in the case of reftests a node may have corresponding manifest
         items without being a test itself."""
@@ -940,11 +856,11 @@
         drop_cached = "root" not in self.__dict__
 
         if self.name_is_non_test:
-            rv = "support", [
+            rv: Tuple[Text, List[ManifestItem]] = ("support", [
                 SupportFile(
                     self.tests_root,
                     self.rel_path
-                )]  # type: Tuple[Text, List[ManifestItem]]
+                )])
 
         elif self.name_is_manual:
             rv = ManualTest.item_type, [
@@ -1026,7 +942,7 @@
                     globals = value
                     break
 
-            tests = [
+            tests: List[ManifestItem] = [
                 TestharnessTest(
                     self.tests_root,
                     self.rel_path,
@@ -1039,7 +955,7 @@
                 )
                 for (suffix, jsshell) in sorted(global_suffixes(globals))
                 for variant in self.test_variants
-            ]   # type: List[ManifestItem]
+            ]
             rv = TestharnessTest.item_type, tests
 
         elif self.name_is_worker:
diff --git a/third_party/wpt_tools/wpt/tools/manifest/testpaths.py b/third_party/wpt_tools/wpt/tools/manifest/testpaths.py
index 6902f0c..2fa5393 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/testpaths.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/testpaths.py
@@ -2,31 +2,21 @@
 import json
 import os
 from collections import defaultdict
+from typing import Any, Dict, Iterable, List, Text
 
 from .manifest import load_and_update, Manifest
 from .log import get_logger
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Dict
-    from typing import Iterable
-    from typing import List
-    from typing import Text
-
 wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
 
 logger = get_logger()
 
 
-def abs_path(path):
-    # type: (str) -> str
+def abs_path(path: str) -> str:
     return os.path.abspath(os.path.expanduser(path))
 
 
-def create_parser():
-    # type: () -> argparse.ArgumentParser
+def create_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "-p", "--path", type=abs_path, help="Path to manifest file.")
@@ -55,10 +45,9 @@
     return parser
 
 
-def get_path_id_map(src_root, tests_root, manifest_file, test_ids):
-    # type: (Text, Text, Manifest, Iterable[Text]) -> Dict[Text, List[Text]]
+def get_path_id_map(src_root: Text, tests_root: Text, manifest_file: Manifest, test_ids: Iterable[Text]) -> Dict[Text, List[Text]]:
     test_ids = set(test_ids)
-    path_id_map = defaultdict(list)  # type: Dict[Text, List[Text]]
+    path_id_map: Dict[Text, List[Text]] = defaultdict(list)
 
     compute_rel_path = src_root != tests_root
 
@@ -74,8 +63,7 @@
     return path_id_map
 
 
-def get_paths(**kwargs):
-    # type: (**Any) -> Dict[Text, List[Text]]
+def get_paths(**kwargs: Any) -> Dict[Text, List[Text]]:
     tests_root = kwargs["tests_root"]
     assert tests_root is not None
     path = kwargs["path"]
@@ -95,8 +83,7 @@
     return get_path_id_map(src_root, tests_root, manifest_file, kwargs["test_ids"])
 
 
-def write_output(path_id_map, as_json):
-    # type: (Dict[Text, List[Text]], bool) -> None
+def write_output(path_id_map: Dict[Text, List[Text]], as_json: bool) -> None:
     if as_json:
         print(json.dumps(path_id_map))
     else:
@@ -106,7 +93,6 @@
                 print("  " + test_id)
 
 
-def run(**kwargs):
-    # type: (**Any) -> None
+def run(**kwargs: Any) -> None:
     path_id_map = get_paths(**kwargs)
     write_output(path_id_map, as_json=kwargs["json"])
diff --git a/third_party/wpt_tools/wpt/tools/manifest/typedata.py b/third_party/wpt_tools/wpt/tools/manifest/typedata.py
index 4061c9e..746a42c9 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/typedata.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/typedata.py
@@ -1,36 +1,18 @@
-from collections.abc import MutableMapping
+from typing import (Any, Dict, Iterator, List, Optional, MutableMapping, Set, Text, Tuple,
+                    Type, TYPE_CHECKING, Union)
 
+from .item import ManifestItem
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Dict
-    from typing import Iterator
-    from typing import List
-    from typing import Optional
-    from typing import Set
-    from typing import Text
-    from typing import Tuple
-    from typing import Type
-    from typing import Union
-
+if TYPE_CHECKING:
     # avoid actually importing these, they're only used by type comments
-    from . import item
-    from . import manifest
+    from .manifest import Manifest
 
-
-if MYPY:
-    TypeDataType = MutableMapping[Tuple[str, ...], Set[item.ManifestItem]]
-    PathHashType = MutableMapping[Tuple[str, ...], str]
-else:
-    TypeDataType = MutableMapping
-    PathHashType = MutableMapping
+TypeDataType = MutableMapping[Tuple[str, ...], Set[ManifestItem]]
+PathHashType = MutableMapping[Tuple[str, ...], str]
 
 
 class TypeData(TypeDataType):
-    def __init__(self, m, type_cls):
-        # type: (manifest.Manifest, Type[item.ManifestItem]) -> None
+    def __init__(self, m: "Manifest", type_cls: Type[ManifestItem]) -> None:
         """Dict-like object containing the TestItems for each test type.
 
         Loading an actual Item class for each test is unnecessarily
@@ -42,14 +24,13 @@
         from iteration, we do egerly load all items when iterating
         over the class."""
         self._manifest = m
-        self._type_cls = type_cls  # type: Type[item.ManifestItem]
-        self._json_data = {}  # type: Dict[Text, Any]
-        self._data = {}  # type: Dict[Text, Any]
-        self._hashes = {}  # type: Dict[Tuple[Text, ...], Text]
+        self._type_cls: Type[ManifestItem] = type_cls
+        self._json_data: Dict[Text, Any] = {}
+        self._data: Dict[Text, Any] = {}
+        self._hashes: Dict[Tuple[Text, ...], Text] = {}
         self.hashes = PathHash(self)
 
-    def _delete_node(self, data, key):
-        # type: (Dict[Text, Any], Tuple[Text, ...]) -> None
+    def _delete_node(self, data: Dict[Text, Any], key: Tuple[Text, ...]) -> None:
         """delete a path from a Dict data with a given key"""
         path = []
         node = data
@@ -67,9 +48,8 @@
             else:
                 break
 
-    def __getitem__(self, key):
-        # type: (Tuple[Text, ...]) -> Set[item.ManifestItem]
-        node = self._data  # type: Union[Dict[Text, Any], Set[item.ManifestItem], List[Any]]
+    def __getitem__(self, key: Tuple[Text, ...]) -> Set[ManifestItem]:
+        node: Union[Dict[Text, Any], Set[ManifestItem], List[Any]] = self._data
         for pathseg in key:
             if isinstance(node, dict) and pathseg in node:
                 node = node[pathseg]
@@ -117,8 +97,7 @@
 
         return data
 
-    def __setitem__(self, key, value):
-        # type: (Tuple[Text, ...], Set[item.ManifestItem]) -> None
+    def __setitem__(self, key: Tuple[Text, ...], value: Set[ManifestItem]) -> None:
         try:
             self._delete_node(self._json_data, key)
         except KeyError:
@@ -131,8 +110,7 @@
                 raise KeyError(f"{key!r} is a child of a test ({key[:i+1]!r})")
         node[key[-1]] = value
 
-    def __delitem__(self, key):
-        # type: (Tuple[Text, ...]) -> None
+    def __delitem__(self, key: Tuple[Text, ...]) -> None:
         try:
             self._delete_node(self._data, key)
         except KeyError:
@@ -143,12 +121,11 @@
             except KeyError:
                 pass
 
-    def __iter__(self):
-        # type: () -> Iterator[Tuple[Text, ...]]
+    def __iter__(self) -> Iterator[Tuple[Text, ...]]:
         """Iterator over keys in the TypeData in codepoint order"""
-        data_node = self._data  # type: Optional[Union[Dict[Text, Any], Set[item.ManifestItem]]]
-        json_node = self._json_data  # type: Optional[Union[Dict[Text, Any], List[Any]]]
-        path = tuple()  # type: Tuple[Text, ...]
+        data_node: Optional[Union[Dict[Text, Any], Set[ManifestItem]]] = self._data
+        json_node: Optional[Union[Dict[Text, Any], List[Any]]] = self._json_data
+        path: Tuple[Text, ...] = tuple()
         stack = [(data_node, json_node, path)]
         while stack:
             data_node, json_node, path = stack.pop()
@@ -159,7 +136,7 @@
                 assert data_node is None or isinstance(data_node, dict)
                 assert json_node is None or isinstance(json_node, dict)
 
-                keys = set()  # type: Set[Text]
+                keys: Set[Text] = set()
                 if data_node is not None:
                     keys |= set(iter(data_node))
                 if json_node is not None:
@@ -170,11 +147,10 @@
                                   json_node.get(key) if json_node is not None else None,
                                   path + (key,)))
 
-    def __len__(self):
-        # type: () -> int
+    def __len__(self) -> int:
         count = 0
 
-        stack = [self._data]  # type: List[Union[Dict[Text, Any], Set[item.ManifestItem]]]
+        stack: List[Union[Dict[Text, Any], Set[ManifestItem]]] = [self._data]
         while stack:
             v = stack.pop()
             if isinstance(v, set):
@@ -182,7 +158,7 @@
             else:
                 stack.extend(v.values())
 
-        json_stack = [self._json_data]  # type: List[Union[Dict[Text, Any], List[Any]]]
+        json_stack: List[Union[Dict[Text, Any], List[Any]]] = [self._json_data]
         while json_stack:
             json_v = json_stack.pop()
             if isinstance(json_v, list):
@@ -192,14 +168,12 @@
 
         return count
 
-    def __nonzero__(self):
-        # type: () -> bool
+    def __nonzero__(self) -> bool:
         return bool(self._data) or bool(self._json_data)
 
     __bool__ = __nonzero__
 
-    def __contains__(self, key):
-        # type: (Any) -> bool
+    def __contains__(self, key: Any) -> bool:
         # we provide our own impl of this to avoid calling __getitem__ and generating items for
         # those in self._json_data
         node = self._data
@@ -222,15 +196,13 @@
 
         return False
 
-    def clear(self):
-        # type: () -> None
+    def clear(self) -> None:
         # much, much simpler/quicker than that defined in MutableMapping
         self._json_data.clear()
         self._data.clear()
         self._hashes.clear()
 
-    def set_json(self, json_data):
-        # type: (Dict[Text, Any]) -> None
+    def set_json(self, json_data: Dict[Text, Any]) -> None:
         """Provide the object with a raw JSON blob
 
         Note that this object graph is assumed to be owned by the TypeData
@@ -242,8 +214,7 @@
 
         self._json_data = json_data
 
-    def to_json(self):
-        # type: () -> Dict[Text, Any]
+    def to_json(self) -> Dict[Text, Any]:
         """Convert the current data to JSON
 
         Note that the returned object may contain references to the internal
@@ -254,15 +225,14 @@
         """
         json_rv = self._json_data.copy()
 
-        def safe_sorter(element):
-            # type: (Tuple[str,str]) -> Tuple[str,str]
+        def safe_sorter(element: Tuple[str,str]) -> Tuple[str,str]:
             """key function to sort lists with None values."""
             if element and not element[0]:
                 return ("", element[1])
             else:
                 return element
 
-        stack = [(self._data, json_rv, tuple())]  # type: List[Tuple[Dict[Text, Any], Dict[Text, Any], Tuple[Text, ...]]]
+        stack: List[Tuple[Dict[Text, Any], Dict[Text, Any], Tuple[Text, ...]]] = [(self._data, json_rv, tuple())]
         while stack:
             data_node, json_node, par_full_key = stack.pop()
             for k, v in data_node.items():
@@ -279,12 +249,10 @@
 
 
 class PathHash(PathHashType):
-    def __init__(self, data):
-        # type: (TypeData) -> None
+    def __init__(self, data: TypeData) -> None:
         self._data = data
 
-    def __getitem__(self, k):
-        # type: (Tuple[Text, ...]) -> Text
+    def __getitem__(self, k: Tuple[Text, ...]) -> Text:
         if k not in self._data:
             raise KeyError
 
@@ -303,8 +271,7 @@
         assert False, "unreachable"
         raise KeyError
 
-    def __setitem__(self, k, v):
-        # type: (Tuple[Text, ...], Text) -> None
+    def __setitem__(self, k: Tuple[Text, ...], v: Text) -> None:
         if k not in self._data:
             raise KeyError
 
@@ -323,14 +290,11 @@
 
         self._data._hashes[k] = v
 
-    def __delitem__(self, k):
-        # type: (Tuple[Text, ...]) -> None
+    def __delitem__(self, k: Tuple[Text, ...]) -> None:
         raise ValueError("keys here must match underlying data")
 
-    def __iter__(self):
-        # type: () -> Iterator[Tuple[Text, ...]]
+    def __iter__(self) -> Iterator[Tuple[Text, ...]]:
         return iter(self._data)
 
-    def __len__(self):
-        # type: () -> int
+    def __len__(self) -> int:
         return len(self._data)
diff --git a/third_party/wpt_tools/wpt/tools/manifest/update.py b/third_party/wpt_tools/wpt/tools/manifest/update.py
index d7ef2082..fef0b96 100755
--- a/third_party/wpt_tools/wpt/tools/manifest/update.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/update.py
@@ -1,11 +1,15 @@
 #!/usr/bin/env python3
 import argparse
 import os
+from typing import Any, Optional, TYPE_CHECKING
 
 from . import manifest
 from . import vcs
 from .log import get_logger, enable_debug_logging
 from .download import download_from_github
+if TYPE_CHECKING:
+    from .manifest import Manifest  # avoid cyclic import
+
 
 here = os.path.dirname(__file__)
 
@@ -13,23 +17,15 @@
 
 logger = get_logger()
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Optional
-    from .manifest import Manifest  # avoid cyclic import
 
-
-def update(tests_root,  # type: str
-           manifest,  # type: Manifest
-           manifest_path=None,  # type: Optional[str]
-           working_copy=True,  # type: bool
-           cache_root=None,  # type: Optional[str]
-           rebuild=False,  # type: bool
-           parallel=True  # type: bool
-           ):
-    # type: (...) -> bool
+def update(tests_root: str,
+           manifest: "Manifest",
+           manifest_path: Optional[str] = None,
+           working_copy: bool = True,
+           cache_root: Optional[str] = None,
+           rebuild: bool = False,
+           parallel: bool = True
+           ) -> bool:
     logger.warning("Deprecated; use manifest.load_and_update instead")
     logger.info("Updating manifest")
 
@@ -38,8 +34,7 @@
     return manifest.update(tree, parallel)
 
 
-def update_from_cli(**kwargs):
-    # type: (**Any) -> None
+def update_from_cli(**kwargs: Any) -> None:
     tests_root = kwargs["tests_root"]
     path = kwargs["path"]
     assert tests_root is not None
@@ -56,13 +51,11 @@
                              parallel=kwargs["parallel"])
 
 
-def abs_path(path):
-    # type: (str) -> str
+def abs_path(path: str) -> str:
     return os.path.abspath(os.path.expanduser(path))
 
 
-def create_parser():
-    # type: () -> argparse.ArgumentParser
+def create_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "-v", "--verbose", dest="verbose", action="store_true", default=False,
@@ -89,8 +82,7 @@
     return parser
 
 
-def run(*args, **kwargs):
-    # type: (*Any, **Any) -> None
+def run(*args: Any, **kwargs: Any) -> None:
     if kwargs["path"] is None:
         kwargs["path"] = os.path.join(kwargs["tests_root"], "MANIFEST.json")
     if kwargs["verbose"]:
@@ -98,8 +90,7 @@
     update_from_cli(**kwargs)
 
 
-def main():
-    # type: () -> None
+def main() -> None:
     opts = create_parser().parse_args()
 
     run(**vars(opts))
diff --git a/third_party/wpt_tools/wpt/tools/manifest/utils.py b/third_party/wpt_tools/wpt/tools/manifest/utils.py
index 59ddb66..7ccd3af 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/utils.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/utils.py
@@ -1,26 +1,11 @@
 import os
 import subprocess
 import sys
-
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Text
-    from typing import Callable
-    from typing import Any
-    from typing import Generic
-    from typing import TypeVar
-    from typing import Optional
-    T = TypeVar("T")
-else:
-    # eww, eww, ewwww
-    Generic = {}
-    T = object()
-    Generic[T] = object
+from typing import Any, Callable, Generic, Optional, Text, TypeVar
+T = TypeVar("T")
 
 
-def rel_path_to_url(rel_path, url_base="/"):
-    # type: (Text, Text) -> Text
+def rel_path_to_url(rel_path: Text, url_base: Text = "/") -> Text:
     assert not os.path.isabs(rel_path), rel_path
     if url_base[0] != "/":
         url_base = "/" + url_base
@@ -29,8 +14,7 @@
     return url_base + rel_path.replace(os.sep, "/")
 
 
-def from_os_path(path):
-    # type: (Text) -> Text
+def from_os_path(path: Text) -> Text:
     assert os.path.sep == "/" or sys.platform == "win32"
     if "/" == os.path.sep:
         rv = path
@@ -41,8 +25,7 @@
     return rv
 
 
-def to_os_path(path):
-    # type: (Text) -> Text
+def to_os_path(path: Text) -> Text:
     assert os.path.sep == "/" or sys.platform == "win32"
     if "\\" in path:
         raise ValueError("normalised path contains \\")
@@ -51,10 +34,8 @@
     return path.replace("/", os.path.sep)
 
 
-def git(path):
-    # type: (Text) -> Optional[Callable[..., Text]]
-    def gitfunc(cmd, *args):
-        # type: (Text, *Text) -> Text
+def git(path: Text) -> Optional[Callable[..., Text]]:
+    def gitfunc(cmd: Text, *args: Text) -> Text:
         full_cmd = ["git", cmd] + list(args)
         try:
             return subprocess.check_output(full_cmd, cwd=path, stderr=subprocess.STDOUT).decode('utf8')
@@ -75,14 +56,12 @@
 
 
 class cached_property(Generic[T]):
-    def __init__(self, func):
-        # type: (Callable[[Any], T]) -> None
+    def __init__(self, func: Callable[[Any], T]) -> None:
         self.func = func
         self.__doc__ = getattr(func, "__doc__")
         self.name = func.__name__
 
-    def __get__(self, obj, cls=None):
-        # type: (Any, Optional[type]) -> T
+    def __get__(self, obj: Any, cls: Optional[type] = None) -> T:
         if obj is None:
             return self  # type: ignore
 
diff --git a/third_party/wpt_tools/wpt/tools/manifest/vcs.py b/third_party/wpt_tools/wpt/tools/manifest/vcs.py
index ec59f42a..554e68a 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/vcs.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/vcs.py
@@ -2,7 +2,9 @@
 import os
 import stat
 from collections import deque
-from collections.abc import MutableMapping
+from os import stat_result
+from typing import (Any, Dict, Iterable, Iterator, List, MutableMapping, Optional, Set, Text, Tuple,
+                    TYPE_CHECKING)
 
 from . import jsonlib
 from .utils import git
@@ -12,21 +14,18 @@
 from gitignore import gitignore  # type: ignore
 
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Dict, Optional, List, Set, Text, Iterable, Any, Tuple, Iterator
-    from .manifest import Manifest  # cyclic import under MYPY guard
-    stat_result = os.stat_result
+if TYPE_CHECKING:
+    from .manifest import Manifest  # avoid cyclic import
 
-    GitIgnoreCacheType = MutableMapping[bytes, bool]
-else:
-    GitIgnoreCacheType = MutableMapping
+GitIgnoreCacheType = MutableMapping[bytes, bool]
 
 
-def get_tree(tests_root, manifest, manifest_path, cache_root,
-             working_copy=True, rebuild=False):
-    # type: (Text, Manifest, Optional[Text], Optional[Text], bool, bool) -> FileSystem
+def get_tree(tests_root: Text,
+             manifest: "Manifest",
+             manifest_path: Optional[Text],
+             cache_root: Optional[Text],
+             working_copy: bool = True,
+             rebuild: bool = False) -> "FileSystem":
     tree = None
     if cache_root is None:
         cache_root = os.path.join(tests_root, ".wptcache")
@@ -49,12 +48,10 @@
 
 
 class GitHasher:
-    def __init__(self, path):
-        # type: (Text) -> None
+    def __init__(self, path: Text) -> None:
         self.git = git(path)
 
-    def _local_changes(self):
-        # type: () -> Set[Text]
+    def _local_changes(self) -> Set[Text]:
         """get a set of files which have changed between HEAD and working copy"""
         assert self.git is not None
         # note that git runs the command with tests_root as the cwd, which may
@@ -63,12 +60,11 @@
         data = self.git(*cmd)
         return set(data.split("\0"))
 
-    def hash_cache(self):
-        # type: () -> Dict[Text, Optional[Text]]
+    def hash_cache(self) -> Dict[Text, Optional[Text]]:
         """
         A dict of rel_path -> current git object id if the working tree matches HEAD else None
         """
-        hash_cache = {}  # type: Dict[Text, Optional[Text]]
+        hash_cache: Dict[Text, Optional[Text]] = {}
 
         if self.git is None:
             return hash_cache
@@ -86,8 +82,12 @@
 
 
 class FileSystem:
-    def __init__(self, tests_root, url_base, cache_path, manifest_path=None, rebuild=False):
-        # type: (Text, Text, Optional[Text], Optional[Text], bool) -> None
+    def __init__(self,
+                 tests_root: Text,
+                 url_base: Text,
+                 cache_path: Optional[Text],
+                 manifest_path: Optional[Text] = None,
+                 rebuild: bool = False) -> None:
         self.tests_root = tests_root
         self.url_base = url_base
         self.ignore_cache = None
@@ -104,8 +104,7 @@
         git = GitHasher(tests_root)
         self.hash_cache = git.hash_cache()
 
-    def __iter__(self):
-        # type: () -> Iterator[Tuple[Text, Optional[Text], bool]]
+    def __iter__(self) -> Iterator[Tuple[Text, Optional[Text], bool]]:
         mtime_cache = self.mtime_cache
         for dirpath, dirnames, filenames in self.path_filter(
                 walk(self.tests_root.encode("utf8"))):
@@ -117,16 +116,14 @@
                 else:
                     yield path, None, False
 
-    def dump_caches(self):
-        # type: () -> None
+    def dump_caches(self) -> None:
         for cache in [self.mtime_cache, self.ignore_cache]:
             if cache is not None:
                 cache.dump()
 
 
 class CacheFile(metaclass=abc.ABCMeta):
-    def __init__(self, cache_root, tests_root, rebuild=False):
-        # type: (Text, Text, bool) -> None
+    def __init__(self, cache_root: Text, tests_root: Text, rebuild: bool = False) -> None:
         self.tests_root = tests_root
         if not os.path.exists(cache_root):
             os.makedirs(cache_root)
@@ -135,20 +132,17 @@
         self.data = self.load(rebuild)
 
     @abc.abstractproperty
-    def file_name(self):
-        # type: () -> Text
+    def file_name(self) -> Text:
         pass
 
-    def dump(self):
-        # type: () -> None
+    def dump(self) -> None:
         if not self.modified:
             return
         with open(self.path, 'w') as f:
             jsonlib.dump_local(self.data, f)
 
-    def load(self, rebuild=False):
-        # type: (bool) -> Dict[Text, Any]
-        data = {}  # type: Dict[Text, Any]
+    def load(self, rebuild: bool = False) -> Dict[Text, Any]:
+        data: Dict[Text, Any] = {}
         try:
             if not rebuild:
                 with open(self.path) as f:
@@ -161,8 +155,7 @@
             pass
         return data
 
-    def check_valid(self, data):
-        # type: (Dict[Text, Any]) -> Dict[Text, Any]
+    def check_valid(self, data: Dict[Text, Any]) -> Dict[Text, Any]:
         """Check if the cached data is valid and return an updated copy of the
         cache containing only data that can be used."""
         return data
@@ -171,13 +164,11 @@
 class MtimeCache(CacheFile):
     file_name = "mtime.json"
 
-    def __init__(self, cache_root, tests_root, manifest_path, rebuild=False):
-        # type: (Text, Text, Text, bool) -> None
+    def __init__(self, cache_root: Text, tests_root: Text, manifest_path: Text, rebuild: bool = False) -> None:
         self.manifest_path = manifest_path
         super().__init__(cache_root, tests_root, rebuild)
 
-    def updated(self, rel_path, stat):
-        # type: (Text, stat_result) -> bool
+    def updated(self, rel_path: Text, stat: stat_result) -> bool:
         """Return a boolean indicating whether the file changed since the cache was last updated.
 
         This implicitly updates the cache with the new mtime data."""
@@ -188,8 +179,7 @@
             return True
         return False
 
-    def check_valid(self, data):
-        # type: (Dict[Any, Any]) -> Dict[Any, Any]
+    def check_valid(self, data: Dict[Any, Any]) -> Dict[Any, Any]:
         if data.get("/tests_root") != self.tests_root:
             self.modified = True
         else:
@@ -204,8 +194,7 @@
             data["/tests_root"] = self.tests_root
         return data
 
-    def dump(self):
-        # type: () -> None
+    def dump(self) -> None:
         if self.manifest_path is None:
             raise ValueError
         if not os.path.exists(self.manifest_path):
@@ -219,8 +208,7 @@
 class GitIgnoreCache(CacheFile, GitIgnoreCacheType):
     file_name = "gitignore2.json"
 
-    def check_valid(self, data):
-        # type: (Dict[Any, Any]) -> Dict[Any, Any]
+    def check_valid(self, data: Dict[Any, Any]) -> Dict[Any, Any]:
         ignore_path = os.path.join(self.tests_root, ".gitignore")
         mtime = os.path.getmtime(ignore_path)
         if data.get("/gitignore_file") != [ignore_path, mtime]:
@@ -229,8 +217,7 @@
             data["/gitignore_file"] = [ignore_path, mtime]
         return data
 
-    def __contains__(self, key):
-        # type: (Any) -> bool
+    def __contains__(self, key: Any) -> bool:
         try:
             key = key.decode("utf-8")
         except Exception:
@@ -238,36 +225,30 @@
 
         return key in self.data
 
-    def __getitem__(self, key):
-        # type: (bytes) -> bool
+    def __getitem__(self, key: bytes) -> bool:
         real_key = key.decode("utf-8")
         v = self.data[real_key]
         assert isinstance(v, bool)
         return v
 
-    def __setitem__(self, key, value):
-        # type: (bytes, bool) -> None
+    def __setitem__(self, key: bytes, value: bool) -> None:
         real_key = key.decode("utf-8")
         if self.data.get(real_key) != value:
             self.modified = True
             self.data[real_key] = value
 
-    def __delitem__(self, key):
-        # type: (bytes) -> None
+    def __delitem__(self, key: bytes) -> None:
         real_key = key.decode("utf-8")
         del self.data[real_key]
 
-    def __iter__(self):
-        # type: () -> Iterator[bytes]
+    def __iter__(self) -> Iterator[bytes]:
         return (key.encode("utf-8") for key in self.data)
 
-    def __len__(self):
-        # type: () -> int
+    def __len__(self) -> int:
         return len(self.data)
 
 
-def walk(root):
-    # type: (bytes) -> Iterable[Tuple[bytes, List[Tuple[bytes, stat_result]], List[Tuple[bytes, stat_result]]]]
+def walk(root: bytes) -> Iterable[Tuple[bytes, List[Tuple[bytes, stat_result]], List[Tuple[bytes, stat_result]]]]:
     """Re-implementation of os.walk. Returns an iterator over
     (dirpath, dirnames, filenames), with some semantic differences
     to os.walk.
diff --git a/third_party/wpt_tools/wpt/tools/serve/serve.py b/third_party/wpt_tools/wpt/tools/serve/serve.py
index 52a0eae..e5aa5f0 100644
--- a/third_party/wpt_tools/wpt/tools/serve/serve.py
+++ b/third_party/wpt_tools/wpt/tools/serve/serve.py
@@ -19,7 +19,7 @@
 from io import IOBase
 from itertools import chain, product
 from html5lib import html5parser
-from typing import ClassVar, List, Set, Tuple
+from typing import ClassVar, List, Optional, Set, Tuple
 
 from localpaths import repo_root  # type: ignore
 
@@ -97,7 +97,7 @@
 
     __meta__ = abc.ABCMeta
 
-    headers = []  # type: ClassVar[List[Tuple[str, str]]]
+    headers: ClassVar[List[Tuple[str, str]]] = []
 
     def __init__(self, base_path=None, url_base="/"):
         self.base_path = base_path
@@ -214,18 +214,18 @@
 
 
 class HtmlWrapperHandler(WrapperHandler):
-    global_type = None  # type: ClassVar[str]
+    global_type: ClassVar[Optional[str]] = None
     headers = [('Content-Type', 'text/html')]
 
     def check_exposure(self, request):
-        if self.global_type:
-            globals = ""
+        if self.global_type is not None:
+            global_variants = ""
             for (key, value) in self._get_metadata(request):
                 if key == "global":
-                    globals = value
+                    global_variants = value
                     break
 
-            if self.global_type not in parse_variants(globals):
+            if self.global_type not in parse_variants(global_variants):
                 raise HTTPException(404, "This test cannot be loaded in %s mode" %
                                     self.global_type)
 
diff --git a/third_party/wpt_tools/wpt/tools/third_party/enum/enum/LICENSE b/third_party/wpt_tools/wpt/tools/third_party/enum/enum/LICENSE
deleted file mode 100644
index 9003b885..0000000
--- a/third_party/wpt_tools/wpt/tools/third_party/enum/enum/LICENSE
+++ /dev/null
@@ -1,32 +0,0 @@
-Copyright (c) 2013, Ethan Furman.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-    Redistributions of source code must retain the above
-    copyright notice, this list of conditions and the
-    following disclaimer.
-
-    Redistributions in binary form must reproduce the above
-    copyright notice, this list of conditions and the following
-    disclaimer in the documentation and/or other materials
-    provided with the distribution.
-
-    Neither the name Ethan Furman nor the names of any
-    contributors may be used to endorse or promote products
-    derived from this software without specific prior written
-    permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/wpt_tools/wpt/tools/third_party/enum/enum/README b/third_party/wpt_tools/wpt/tools/third_party/enum/enum/README
deleted file mode 100644
index aa2333d8..0000000
--- a/third_party/wpt_tools/wpt/tools/third_party/enum/enum/README
+++ /dev/null
@@ -1,3 +0,0 @@
-enum34 is the new Python stdlib enum module available in Python 3.4
-backported for previous versions of Python from 2.4 to 3.3.
-tested on 2.6, 2.7, and 3.3+
diff --git a/third_party/wpt_tools/wpt/tools/third_party/enum/enum/__init__.py b/third_party/wpt_tools/wpt/tools/third_party/enum/enum/__init__.py
deleted file mode 100644
index 51f3cf2..0000000
--- a/third_party/wpt_tools/wpt/tools/third_party/enum/enum/__init__.py
+++ /dev/null
@@ -1,838 +0,0 @@
-"""Python Enumerations"""
-
-import sys as _sys
-
-__all__ = ['Enum', 'IntEnum', 'unique']
-
-version = 1, 1, 10
-
-pyver = float('%s.%s' % _sys.version_info[:2])
-
-try:
-    any
-except NameError:
-    def any(iterable):
-        for element in iterable:
-            if element:
-                return True
-        return False
-
-try:
-    from collections import OrderedDict
-except ImportError:
-    OrderedDict = None
-
-try:
-    basestring
-except NameError:
-    # In Python 2 basestring is the ancestor of both str and unicode
-    # in Python 3 it's just str, but was missing in 3.1
-    basestring = str
-
-try:
-    unicode
-except NameError:
-    # In Python 3 unicode no longer exists (it's just str)
-    unicode = str
-
-class _RouteClassAttributeToGetattr(object):
-    """Route attribute access on a class to __getattr__.
-
-    This is a descriptor, used to define attributes that act differently when
-    accessed through an instance and through a class.  Instance access remains
-    normal, but access to an attribute through a class will be routed to the
-    class's __getattr__ method; this is done by raising AttributeError.
-
-    """
-    def __init__(self, fget=None):
-        self.fget = fget
-
-    def __get__(self, instance, ownerclass=None):
-        if instance is None:
-            raise AttributeError()
-        return self.fget(instance)
-
-    def __set__(self, instance, value):
-        raise AttributeError("can't set attribute")
-
-    def __delete__(self, instance):
-        raise AttributeError("can't delete attribute")
-
-
-def _is_descriptor(obj):
-    """Returns True if obj is a descriptor, False otherwise."""
-    return (
-            hasattr(obj, '__get__') or
-            hasattr(obj, '__set__') or
-            hasattr(obj, '__delete__'))
-
-
-def _is_dunder(name):
-    """Returns True if a __dunder__ name, False otherwise."""
-    return (name[:2] == name[-2:] == '__' and
-            name[2:3] != '_' and
-            name[-3:-2] != '_' and
-            len(name) > 4)
-
-
-def _is_sunder(name):
-    """Returns True if a _sunder_ name, False otherwise."""
-    return (name[0] == name[-1] == '_' and
-            name[1:2] != '_' and
-            name[-2:-1] != '_' and
-            len(name) > 2)
-
-
-def _make_class_unpicklable(cls):
-    """Make the given class un-picklable."""
-    def _break_on_call_reduce(self, protocol=None):
-        raise TypeError('%r cannot be pickled' % self)
-    cls.__reduce_ex__ = _break_on_call_reduce
-    cls.__module__ = '<unknown>'
-
-
-class _EnumDict(dict):
-    """Track enum member order and ensure member names are not reused.
-
-    EnumMeta will use the names found in self._member_names as the
-    enumeration member names.
-
-    """
-    def __init__(self):
-        super(_EnumDict, self).__init__()
-        self._member_names = []
-
-    def __setitem__(self, key, value):
-        """Changes anything not dundered or not a descriptor.
-
-        If a descriptor is added with the same name as an enum member, the name
-        is removed from _member_names (this may leave a hole in the numerical
-        sequence of values).
-
-        If an enum member name is used twice, an error is raised; duplicate
-        values are not checked for.
-
-        Single underscore (sunder) names are reserved.
-
-        Note:   in 3.x __order__ is simply discarded as a not necessary piece
-                leftover from 2.x
-
-        """
-        if pyver >= 3.0 and key in ('_order_', '__order__'):
-            return
-        elif key == '__order__':
-            key = '_order_'
-        if _is_sunder(key):
-            if key != '_order_':
-                raise ValueError('_names_ are reserved for future Enum use')
-        elif _is_dunder(key):
-            pass
-        elif key in self._member_names:
-            # descriptor overwriting an enum?
-            raise TypeError('Attempted to reuse key: %r' % key)
-        elif not _is_descriptor(value):
-            if key in self:
-                # enum overwriting a descriptor?
-                raise TypeError('Key already defined as: %r' % self[key])
-            self._member_names.append(key)
-        super(_EnumDict, self).__setitem__(key, value)
-
-
-# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
-# EnumMeta finishes running the first time the Enum class doesn't exist.  This
-# is also why there are checks in EnumMeta like `if Enum is not None`
-Enum = None
-
-
-class EnumMeta(type):
-    """Metaclass for Enum"""
-    @classmethod
-    def __prepare__(metacls, cls, bases):
-        return _EnumDict()
-
-    def __new__(metacls, cls, bases, classdict):
-        # an Enum class is final once enumeration items have been defined; it
-        # cannot be mixed with other types (int, float, etc.) if it has an
-        # inherited __new__ unless a new __new__ is defined (or the resulting
-        # class will fail).
-        if type(classdict) is dict:
-            original_dict = classdict
-            classdict = _EnumDict()
-            for k, v in original_dict.items():
-                classdict[k] = v
-
-        member_type, first_enum = metacls._get_mixins_(bases)
-        __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
-                                                        first_enum)
-        # save enum items into separate mapping so they don't get baked into
-        # the new class
-        members = dict((k, classdict[k]) for k in classdict._member_names)
-        for name in classdict._member_names:
-            del classdict[name]
-
-        # py2 support for definition order
-        _order_ = classdict.get('_order_')
-        if _order_ is None:
-            if pyver < 3.0:
-                try:
-                    _order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])]
-                except TypeError:
-                    _order_ = [name for name in sorted(members.keys())]
-            else:
-                _order_ = classdict._member_names
-        else:
-            del classdict['_order_']
-            if pyver < 3.0:
-                if isinstance(_order_, basestring):
-                    _order_ = _order_.replace(',', ' ').split()
-                aliases = [name for name in members if name not in _order_]
-                _order_ += aliases
-
-        # check for illegal enum names (any others?)
-        invalid_names = set(members) & set(['mro'])
-        if invalid_names:
-            raise ValueError('Invalid enum member name(s): %s' % (
-                ', '.join(invalid_names), ))
-
-        # save attributes from super classes so we know if we can take
-        # the shortcut of storing members in the class dict
-        base_attributes = set([a for b in bases for a in b.__dict__])
-        # create our new Enum type
-        enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)
-        enum_class._member_names_ = []               # names in random order
-        if OrderedDict is not None:
-            enum_class._member_map_ = OrderedDict()
-        else:
-            enum_class._member_map_ = {}             # name->value map
-        enum_class._member_type_ = member_type
-
-        # Reverse value->name map for hashable values.
-        enum_class._value2member_map_ = {}
-
-        # instantiate them, checking for duplicates as we go
-        # we instantiate first instead of checking for duplicates first in case
-        # a custom __new__ is doing something funky with the values -- such as
-        # auto-numbering ;)
-        if __new__ is None:
-            __new__ = enum_class.__new__
-        for member_name in _order_:
-            value = members[member_name]
-            if not isinstance(value, tuple):
-                args = (value, )
-            else:
-                args = value
-            if member_type is tuple:   # special case for tuple enums
-                args = (args, )     # wrap it one more time
-            if not use_args or not args:
-                enum_member = __new__(enum_class)
-                if not hasattr(enum_member, '_value_'):
-                    enum_member._value_ = value
-            else:
-                enum_member = __new__(enum_class, *args)
-                if not hasattr(enum_member, '_value_'):
-                    enum_member._value_ = member_type(*args)
-            value = enum_member._value_
-            enum_member._name_ = member_name
-            enum_member.__objclass__ = enum_class
-            enum_member.__init__(*args)
-            # If another member with the same value was already defined, the
-            # new member becomes an alias to the existing one.
-            for name, canonical_member in enum_class._member_map_.items():
-                if canonical_member.value == enum_member._value_:
-                    enum_member = canonical_member
-                    break
-            else:
-                # Aliases don't appear in member names (only in __members__).
-                enum_class._member_names_.append(member_name)
-            # performance boost for any member that would not shadow
-            # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr)
-            if member_name not in base_attributes:
-                setattr(enum_class, member_name, enum_member)
-            # now add to _member_map_
-            enum_class._member_map_[member_name] = enum_member
-            try:
-                # This may fail if value is not hashable. We can't add the value
-                # to the map, and by-value lookups for this value will be
-                # linear.
-                enum_class._value2member_map_[value] = enum_member
-            except TypeError:
-                pass
-
-
-        # If a custom type is mixed into the Enum, and it does not know how
-        # to pickle itself, pickle.dumps will succeed but pickle.loads will
-        # fail.  Rather than have the error show up later and possibly far
-        # from the source, sabotage the pickle protocol for this class so
-        # that pickle.dumps also fails.
-        #
-        # However, if the new class implements its own __reduce_ex__, do not
-        # sabotage -- it's on them to make sure it works correctly.  We use
-        # __reduce_ex__ instead of any of the others as it is preferred by
-        # pickle over __reduce__, and it handles all pickle protocols.
-        unpicklable = False
-        if '__reduce_ex__' not in classdict:
-            if member_type is not object:
-                methods = ('__getnewargs_ex__', '__getnewargs__',
-                        '__reduce_ex__', '__reduce__')
-                if not any(m in member_type.__dict__ for m in methods):
-                    _make_class_unpicklable(enum_class)
-                    unpicklable = True
-
-
-        # double check that repr and friends are not the mixin's or various
-        # things break (such as pickle)
-        for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
-            class_method = getattr(enum_class, name)
-            obj_method = getattr(member_type, name, None)
-            enum_method = getattr(first_enum, name, None)
-            if name not in classdict and class_method is not enum_method:
-                if name == '__reduce_ex__' and unpicklable:
-                    continue
-                setattr(enum_class, name, enum_method)
-
-        # method resolution and int's are not playing nice
-        # Python's less than 2.6 use __cmp__
-
-        if pyver < 2.6:
-
-            if issubclass(enum_class, int):
-                setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
-
-        elif pyver < 3.0:
-
-            if issubclass(enum_class, int):
-                for method in (
-                        '__le__',
-                        '__lt__',
-                        '__gt__',
-                        '__ge__',
-                        '__eq__',
-                        '__ne__',
-                        '__hash__',
-                        ):
-                    setattr(enum_class, method, getattr(int, method))
-
-        # replace any other __new__ with our own (as long as Enum is not None,
-        # anyway) -- again, this is to support pickle
-        if Enum is not None:
-            # if the user defined their own __new__, save it before it gets
-            # clobbered in case they subclass later
-            if save_new:
-                setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])
-            setattr(enum_class, '__new__', Enum.__dict__['__new__'])
-        return enum_class
-
-    def __bool__(cls):
-        """
-        classes/types should always be True.
-        """
-        return True
-
-    def __call__(cls, value, names=None, module=None, type=None, start=1):
-        """Either returns an existing member, or creates a new enum class.
-
-        This method is used both when an enum class is given a value to match
-        to an enumeration member (i.e. Color(3)) and for the functional API
-        (i.e. Color = Enum('Color', names='red green blue')).
-
-        When used for the functional API: `module`, if set, will be stored in
-        the new class' __module__ attribute; `type`, if set, will be mixed in
-        as the first base class.
-
-        Note: if `module` is not set this routine will attempt to discover the
-        calling module by walking the frame stack; if this is unsuccessful
-        the resulting class will not be pickleable.
-
-        """
-        if names is None:  # simple value lookup
-            return cls.__new__(cls, value)
-        # otherwise, functional API: we're creating a new Enum type
-        return cls._create_(value, names, module=module, type=type, start=start)
-
-    def __contains__(cls, member):
-        return isinstance(member, cls) and member.name in cls._member_map_
-
-    def __delattr__(cls, attr):
-        # nicer error message when someone tries to delete an attribute
-        # (see issue19025).
-        if attr in cls._member_map_:
-            raise AttributeError(
-                    "%s: cannot delete Enum member." % cls.__name__)
-        super(EnumMeta, cls).__delattr__(attr)
-
-    def __dir__(self):
-        return (['__class__', '__doc__', '__members__', '__module__'] +
-                self._member_names_)
-
-    @property
-    def __members__(cls):
-        """Returns a mapping of member name->value.
-
-        This mapping lists all enum members, including aliases. Note that this
-        is a copy of the internal mapping.
-
-        """
-        return cls._member_map_.copy()
-
-    def __getattr__(cls, name):
-        """Return the enum member matching `name`
-
-        We use __getattr__ instead of descriptors or inserting into the enum
-        class' __dict__ in order to support `name` and `value` being both
-        properties for enum members (which live in the class' __dict__) and
-        enum members themselves.
-
-        """
-        if _is_dunder(name):
-            raise AttributeError(name)
-        try:
-            return cls._member_map_[name]
-        except KeyError:
-            raise AttributeError(name)
-
-    def __getitem__(cls, name):
-        return cls._member_map_[name]
-
-    def __iter__(cls):
-        return (cls._member_map_[name] for name in cls._member_names_)
-
-    def __reversed__(cls):
-        return (cls._member_map_[name] for name in reversed(cls._member_names_))
-
-    def __len__(cls):
-        return len(cls._member_names_)
-
-    __nonzero__ = __bool__
-
-    def __repr__(cls):
-        return "<enum %r>" % cls.__name__
-
-    def __setattr__(cls, name, value):
-        """Block attempts to reassign Enum members.
-
-        A simple assignment to the class namespace only changes one of the
-        several possible ways to get an Enum member from the Enum class,
-        resulting in an inconsistent Enumeration.
-
-        """
-        member_map = cls.__dict__.get('_member_map_', {})
-        if name in member_map:
-            raise AttributeError('Cannot reassign members.')
-        super(EnumMeta, cls).__setattr__(name, value)
-
-    def _create_(cls, class_name, names=None, module=None, type=None, start=1):
-        """Convenience method to create a new Enum class.
-
-        `names` can be:
-
-        * A string containing member names, separated either with spaces or
-          commas.  Values are auto-numbered from 1.
-        * An iterable of member names.  Values are auto-numbered from 1.
-        * An iterable of (member name, value) pairs.
-        * A mapping of member name -> value.
-
-        """
-        if pyver < 3.0:
-            # if class_name is unicode, attempt a conversion to ASCII
-            if isinstance(class_name, unicode):
-                try:
-                    class_name = class_name.encode('ascii')
-                except UnicodeEncodeError:
-                    raise TypeError('%r is not representable in ASCII' % class_name)
-        metacls = cls.__class__
-        if type is None:
-            bases = (cls, )
-        else:
-            bases = (type, cls)
-        classdict = metacls.__prepare__(class_name, bases)
-        _order_ = []
-
-        # special processing needed for names?
-        if isinstance(names, basestring):
-            names = names.replace(',', ' ').split()
-        if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
-            names = [(e, i+start) for (i, e) in enumerate(names)]
-
-        # Here, names is either an iterable of (name, value) or a mapping.
-        item = None  # in case names is empty
-        for item in names:
-            if isinstance(item, basestring):
-                member_name, member_value = item, names[item]
-            else:
-                member_name, member_value = item
-            classdict[member_name] = member_value
-            _order_.append(member_name)
-        # only set _order_ in classdict if name/value was not from a mapping
-        if not isinstance(item, basestring):
-            classdict['_order_'] = _order_
-        enum_class = metacls.__new__(metacls, class_name, bases, classdict)
-
-        # TODO: replace the frame hack if a blessed way to know the calling
-        # module is ever developed
-        if module is None:
-            try:
-                module = _sys._getframe(2).f_globals['__name__']
-            except (AttributeError, ValueError):
-                pass
-        if module is None:
-            _make_class_unpicklable(enum_class)
-        else:
-            enum_class.__module__ = module
-
-        return enum_class
-
-    @staticmethod
-    def _get_mixins_(bases):
-        """Returns the type for creating enum members, and the first inherited
-        enum class.
-
-        bases: the tuple of bases that was given to __new__
-
-        """
-        if not bases or Enum is None:
-            return object, Enum
-
-
-        # double check that we are not subclassing a class with existing
-        # enumeration members; while we're at it, see if any other data
-        # type has been mixed in so we can use the correct __new__
-        member_type = first_enum = None
-        for base in bases:
-            if  (base is not Enum and
-                    issubclass(base, Enum) and
-                    base._member_names_):
-                raise TypeError("Cannot extend enumerations")
-        # base is now the last base in bases
-        if not issubclass(base, Enum):
-            raise TypeError("new enumerations must be created as "
-                    "`ClassName([mixin_type,] enum_type)`")
-
-        # get correct mix-in type (either mix-in type of Enum subclass, or
-        # first base if last base is Enum)
-        if not issubclass(bases[0], Enum):
-            member_type = bases[0]     # first data type
-            first_enum = bases[-1]  # enum type
-        else:
-            for base in bases[0].__mro__:
-                # most common: (IntEnum, int, Enum, object)
-                # possible:    (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
-                #               <class 'int'>, <Enum 'Enum'>,
-                #               <class 'object'>)
-                if issubclass(base, Enum):
-                    if first_enum is None:
-                        first_enum = base
-                else:
-                    if member_type is None:
-                        member_type = base
-
-        return member_type, first_enum
-
-    if pyver < 3.0:
-        @staticmethod
-        def _find_new_(classdict, member_type, first_enum):
-            """Returns the __new__ to be used for creating the enum members.
-
-            classdict: the class dictionary given to __new__
-            member_type: the data type whose __new__ will be used by default
-            first_enum: enumeration to check for an overriding __new__
-
-            """
-            # now find the correct __new__, checking to see of one was defined
-            # by the user; also check earlier enum classes in case a __new__ was
-            # saved as __member_new__
-            __new__ = classdict.get('__new__', None)
-            if __new__:
-                return None, True, True      # __new__, save_new, use_args
-
-            N__new__ = getattr(None, '__new__')
-            O__new__ = getattr(object, '__new__')
-            if Enum is None:
-                E__new__ = N__new__
-            else:
-                E__new__ = Enum.__dict__['__new__']
-            # check all possibles for __member_new__ before falling back to
-            # __new__
-            for method in ('__member_new__', '__new__'):
-                for possible in (member_type, first_enum):
-                    try:
-                        target = possible.__dict__[method]
-                    except (AttributeError, KeyError):
-                        target = getattr(possible, method, None)
-                    if target not in [
-                            None,
-                            N__new__,
-                            O__new__,
-                            E__new__,
-                            ]:
-                        if method == '__member_new__':
-                            classdict['__new__'] = target
-                            return None, False, True
-                        if isinstance(target, staticmethod):
-                            target = target.__get__(member_type)
-                        __new__ = target
-                        break
-                if __new__ is not None:
-                    break
-            else:
-                __new__ = object.__new__
-
-            # if a non-object.__new__ is used then whatever value/tuple was
-            # assigned to the enum member name will be passed to __new__ and to the
-            # new enum member's __init__
-            if __new__ is object.__new__:
-                use_args = False
-            else:
-                use_args = True
-
-            return __new__, False, use_args
-    else:
-        @staticmethod
-        def _find_new_(classdict, member_type, first_enum):
-            """Returns the __new__ to be used for creating the enum members.
-
-            classdict: the class dictionary given to __new__
-            member_type: the data type whose __new__ will be used by default
-            first_enum: enumeration to check for an overriding __new__
-
-            """
-            # now find the correct __new__, checking to see of one was defined
-            # by the user; also check earlier enum classes in case a __new__ was
-            # saved as __member_new__
-            __new__ = classdict.get('__new__', None)
-
-            # should __new__ be saved as __member_new__ later?
-            save_new = __new__ is not None
-
-            if __new__ is None:
-                # check all possibles for __member_new__ before falling back to
-                # __new__
-                for method in ('__member_new__', '__new__'):
-                    for possible in (member_type, first_enum):
-                        target = getattr(possible, method, None)
-                        if target not in (
-                                None,
-                                None.__new__,
-                                object.__new__,
-                                Enum.__new__,
-                                ):
-                            __new__ = target
-                            break
-                    if __new__ is not None:
-                        break
-                else:
-                    __new__ = object.__new__
-
-            # if a non-object.__new__ is used then whatever value/tuple was
-            # assigned to the enum member name will be passed to __new__ and to the
-            # new enum member's __init__
-            if __new__ is object.__new__:
-                use_args = False
-            else:
-                use_args = True
-
-            return __new__, save_new, use_args
-
-
-########################################################
-# In order to support Python 2 and 3 with a single
-# codebase we have to create the Enum methods separately
-# and then use the `type(name, bases, dict)` method to
-# create the class.
-########################################################
-temp_enum_dict = {}
-temp_enum_dict['__doc__'] = "Generic enumeration.\n\n    Derive from this class to define new enumerations.\n\n"
-
-def __new__(cls, value):
-    # all enum instances are actually created during class construction
-    # without calling this method; this method is called by the metaclass'
-    # __call__ (i.e. Color(3) ), and by pickle
-    if type(value) is cls:
-        # For lookups like Color(Color.red)
-        value = value.value
-        #return value
-    # by-value search for a matching enum member
-    # see if it's in the reverse mapping (for hashable values)
-    try:
-        if value in cls._value2member_map_:
-            return cls._value2member_map_[value]
-    except TypeError:
-        # not there, now do long search -- O(n) behavior
-        for member in cls._member_map_.values():
-            if member.value == value:
-                return member
-    raise ValueError("%s is not a valid %s" % (value, cls.__name__))
-temp_enum_dict['__new__'] = __new__
-del __new__
-
-def __repr__(self):
-    return "<%s.%s: %r>" % (
-            self.__class__.__name__, self._name_, self._value_)
-temp_enum_dict['__repr__'] = __repr__
-del __repr__
-
-def __str__(self):
-    return "%s.%s" % (self.__class__.__name__, self._name_)
-temp_enum_dict['__str__'] = __str__
-del __str__
-
-if pyver >= 3.0:
-    def __dir__(self):
-        added_behavior = [
-                m
-                for cls in self.__class__.mro()
-                for m in cls.__dict__
-                if m[0] != '_' and m not in self._member_map_
-                ]
-        return (['__class__', '__doc__', '__module__', ] + added_behavior)
-    temp_enum_dict['__dir__'] = __dir__
-    del __dir__
-
-def __format__(self, format_spec):
-    # mixed-in Enums should use the mixed-in type's __format__, otherwise
-    # we can get strange results with the Enum name showing up instead of
-    # the value
-
-    # pure Enum branch
-    if self._member_type_ is object:
-        cls = str
-        val = str(self)
-    # mix-in branch
-    else:
-        cls = self._member_type_
-        val = self.value
-    return cls.__format__(val, format_spec)
-temp_enum_dict['__format__'] = __format__
-del __format__
-
-
-####################################
-# Python's less than 2.6 use __cmp__
-
-if pyver < 2.6:
-
-    def __cmp__(self, other):
-        if type(other) is self.__class__:
-            if self is other:
-                return 0
-            return -1
-        return NotImplemented
-        raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
-    temp_enum_dict['__cmp__'] = __cmp__
-    del __cmp__
-
-else:
-
-    def __le__(self, other):
-        raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
-    temp_enum_dict['__le__'] = __le__
-    del __le__
-
-    def __lt__(self, other):
-        raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
-    temp_enum_dict['__lt__'] = __lt__
-    del __lt__
-
-    def __ge__(self, other):
-        raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
-    temp_enum_dict['__ge__'] = __ge__
-    del __ge__
-
-    def __gt__(self, other):
-        raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
-    temp_enum_dict['__gt__'] = __gt__
-    del __gt__
-
-
-def __eq__(self, other):
-    if type(other) is self.__class__:
-        return self is other
-    return NotImplemented
-temp_enum_dict['__eq__'] = __eq__
-del __eq__
-
-def __ne__(self, other):
-    if type(other) is self.__class__:
-        return self is not other
-    return NotImplemented
-temp_enum_dict['__ne__'] = __ne__
-del __ne__
-
-def __hash__(self):
-    return hash(self._name_)
-temp_enum_dict['__hash__'] = __hash__
-del __hash__
-
-def __reduce_ex__(self, proto):
-    return self.__class__, (self._value_, )
-temp_enum_dict['__reduce_ex__'] = __reduce_ex__
-del __reduce_ex__
-
-# _RouteClassAttributeToGetattr is used to provide access to the `name`
-# and `value` properties of enum members while keeping some measure of
-# protection from modification, while still allowing for an enumeration
-# to have members named `name` and `value`.  This works because enumeration
-# members are not set directly on the enum class -- __getattr__ is
-# used to look them up.
-
-@_RouteClassAttributeToGetattr
-def name(self):
-    return self._name_
-temp_enum_dict['name'] = name
-del name
-
-@_RouteClassAttributeToGetattr
-def value(self):
-    return self._value_
-temp_enum_dict['value'] = value
-del value
-
-@classmethod
-def _convert(cls, name, module, filter, source=None):
-    """
-    Create a new Enum subclass that replaces a collection of global constants
-    """
-    # convert all constants from source (or module) that pass filter() to
-    # a new Enum called name, and export the enum and its members back to
-    # module;
-    # also, replace the __reduce_ex__ method so unpickling works in
-    # previous Python versions
-    module_globals = vars(_sys.modules[module])
-    if source:
-        source = vars(source)
-    else:
-        source = module_globals
-    members = dict((name, value) for name, value in source.items() if filter(name))
-    cls = cls(name, members, module=module)
-    cls.__reduce_ex__ = _reduce_ex_by_name
-    module_globals.update(cls.__members__)
-    module_globals[name] = cls
-    return cls
-temp_enum_dict['_convert'] = _convert
-del _convert
-
-Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
-del temp_enum_dict
-
-# Enum has now been created
-###########################
-
-class IntEnum(int, Enum):
-    """Enum where members are also (and must be) ints"""
-
-def _reduce_ex_by_name(self, proto):
-    return self.name
-
-def unique(enumeration):
-    """Class decorator that ensures only unique members exist in an enumeration."""
-    duplicates = []
-    for name, member in enumeration.__members__.items():
-        if name != member.name:
-            duplicates.append((name, member.name))
-    if duplicates:
-        duplicate_names = ', '.join(
-                ["%s -> %s" % (alias, name) for (alias, name) in duplicates]
-                )
-        raise ValueError('duplicate names found in %r: %s' %
-                (enumeration, duplicate_names)
-                )
-    return enumeration
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
index 5a169aa..6a02e65 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
@@ -9,7 +9,7 @@
     # The error_code class variable is used to map the JSON Error Code (see
     # https://w3c.github.io/webdriver/#errors) to a BidiException subclass.
     # TODO: Match on error and let it be a class variables only.
-    error_code = None  # type: ClassVar[str]
+    error_code: ClassVar[str]
 
     def __init__(self, message: str, stacktrace: Optional[str] = None):
         super()
@@ -82,5 +82,5 @@
 
 _errors: DefaultDict[str, Type[BidiException]] = collections.defaultdict()
 for item in list(locals().values()):
-    if type(item) == type and issubclass(item, BidiException):
+    if type(item) == type and item != BidiException and issubclass(item, BidiException):
         _errors[item.error_code] = item
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py
index abaf569..f2abb47 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py
@@ -7,9 +7,7 @@
 class BrowsingContext(BidiModule):
     @command
     def capture_screenshot(self, context: str) -> Mapping[str, Any]:
-        params: MutableMapping[str, Any] = {
-            "context": context
-        }
+        params: MutableMapping[str, Any] = {"context": context}
 
         return params
 
@@ -28,7 +26,9 @@
         return params
 
     @command
-    def create(self, type_hint: str, reference_context: Optional[str] = None) -> Mapping[str, Any]:
+    def create(self,
+               type_hint: str,
+               reference_context: Optional[str] = None) -> Mapping[str, Any]:
         params: MutableMapping[str, Any] = {"type": type_hint}
 
         if reference_context is not None:
@@ -63,9 +63,10 @@
         return result["contexts"]
 
     @command
-    def navigate(
-        self, context: str, url: str, wait: Optional[str] = None
-    ) -> Mapping[str, Any]:
+    def navigate(self,
+                 context: str,
+                 url: str,
+                 wait: Optional[str] = None) -> Mapping[str, Any]:
         params: MutableMapping[str, Any] = {"context": context, "url": url}
         if wait is not None:
             params["wait"] = wait
@@ -82,20 +83,28 @@
         return result
 
     @command
-    def print(
-        self,
-        context: str,
-        background: Optional[bool] = None,
-        margin: Optional[Mapping[str, Any]] = None,
-        orientation: Optional[str] = None,
-        page: Optional[Mapping[str, Any]] = None,
-        page_ranges: Optional[List[str]] = None,
-        scale: Optional[float] = None,
-        shrink_to_fit: Optional[bool] = None
-    ) -> Mapping[str, Any]:
-        params: MutableMapping[str, Any] = {
-            "context": context
-        }
+    def reload(self,
+               context: str,
+               ignore_cache: Optional[bool] = None,
+               wait: Optional[str] = None) -> Mapping[str, Any]:
+        params: MutableMapping[str, Any] = {"context": context}
+        if ignore_cache is not None:
+            params["ignoreCache"] = ignore_cache
+        if wait is not None:
+            params["wait"] = wait
+        return params
+
+    @command
+    def print(self,
+              context: str,
+              background: Optional[bool] = None,
+              margin: Optional[Mapping[str, Any]] = None,
+              orientation: Optional[str] = None,
+              page: Optional[Mapping[str, Any]] = None,
+              page_ranges: Optional[List[str]] = None,
+              scale: Optional[float] = None,
+              shrink_to_fit: Optional[bool] = None) -> Mapping[str, Any]:
+        params: MutableMapping[str, Any] = {"context": context}
 
         if background is not None:
             params["background"] = background
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/input.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/input.py
index 6b2221d..5240bbb5 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/input.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/input.py
@@ -372,3 +372,7 @@
     def release_actions(self, context: str) -> Mapping[str, Any]:
         params: MutableMapping[str, Any] = {"context": context}
         return params
+
+
+def get_element_origin(element: Any) -> Mapping[str, Any]:
+    return {"type": "element", "element": {"sharedId": element["sharedId"]}}
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/error.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/error.py
index 1b67d33..7d5d914 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/error.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/error.py
@@ -12,8 +12,8 @@
     # However, http_status need not match, and both are set as instance
     # variables, shadowing the class variables. TODO: Match on both http_status
     # and status_code and let these be class variables only.
-    http_status = None  # type: ClassVar[int]
-    status_code = None  # type: ClassVar[str]
+    http_status: ClassVar[int]
+    status_code: ClassVar[str]
 
     def __init__(self, http_status=None, status_code=None, message=None, stacktrace=None):
         super()
@@ -228,5 +228,5 @@
 
 _errors: DefaultDict[str, Type[WebDriverException]] = collections.defaultdict()
 for item in list(locals().values()):
-    if type(item) == type and issubclass(item, WebDriverException):
+    if type(item) == type and item != WebDriverException and issubclass(item, WebDriverException):
         _errors[item.status_code] = item
diff --git a/third_party/wpt_tools/wpt/tools/webtransport/h3/webtransport_h3_server.py b/third_party/wpt_tools/wpt/tools/webtransport/h3/webtransport_h3_server.py
index 141bd9f..0fc367e 100644
--- a/third_party/wpt_tools/wpt/tools/webtransport/h3/webtransport_h3_server.py
+++ b/third_party/wpt_tools/wpt/tools/webtransport/h3/webtransport_h3_server.py
@@ -15,7 +15,7 @@
 from aioquic.buffer import Buffer  # type: ignore
 from aioquic.asyncio import QuicConnectionProtocol, serve  # type: ignore
 from aioquic.asyncio.client import connect  # type: ignore
-from aioquic.h3.connection import H3_ALPN, FrameType, H3Connection, ProtocolError  # type: ignore
+from aioquic.h3.connection import H3_ALPN, FrameType, H3Connection, ProtocolError, SettingsError  # type: ignore
 from aioquic.h3.events import H3Event, HeadersReceived, WebTransportStreamDataReceived, DatagramReceived, DataReceived  # type: ignore
 from aioquic.quic.configuration import QuicConfiguration  # type: ignore
 from aioquic.quic.connection import logger as quic_connection_logger  # type: ignore
@@ -66,12 +66,18 @@
         self._datagram_setting: Optional[H3DatagramSetting] = None
 
     def _validate_settings(self, settings: Dict[int, int]) -> None:
-        super()._validate_settings(settings)
+        # aioquic doesn't recognize the RFC version of HTTP Datagrams yet.
+        # Intentionally don't call `super()._validate_settings(settings)` since
+        # it raises a SettingsError when only the RFC version is negotiated.
         if settings.get(H3DatagramSetting.RFC) == 1:
             self._datagram_setting = H3DatagramSetting.RFC
         elif settings.get(H3DatagramSetting.DRAFT04) == 1:
             self._datagram_setting = H3DatagramSetting.DRAFT04
 
+        if self._datagram_setting is None:
+            raise SettingsError("HTTP Datagrams support required")
+
+
     def _get_local_settings(self) -> Dict[int, int]:
         settings = super()._get_local_settings()
         settings[H3DatagramSetting.RFC] = 1
diff --git a/third_party/wpt_tools/wpt/tools/wpt/browser.py b/third_party/wpt_tools/wpt/tools/wpt/browser.py
index 7a046e8..e40739e 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/browser.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/browser.py
@@ -8,7 +8,7 @@
 import tempfile
 from abc import ABCMeta, abstractmethod
 from datetime import datetime, timedelta, timezone
-from distutils.spawn import find_executable
+from shutil import which
 from urllib.parse import urlsplit
 
 import html5lib
@@ -294,7 +294,7 @@
         binary = None
 
         if self.platform == "linux":
-            binary = find_executable("firefox", os.path.join(path, "firefox"))
+            binary = which("firefox", path=os.path.join(path, "firefox"))
         elif self.platform == "win":
             import mozinstall
             try:
@@ -303,8 +303,10 @@
                 # ignore the case where we fail to get a binary
                 pass
         elif self.platform == "macos":
-            binary = find_executable("firefox", os.path.join(path, self.application_name.get(channel, "Firefox Nightly.app"),
-                                                             "Contents", "MacOS"))
+            binary = which("firefox",
+                           path=os.path.join(path,
+                                             self.application_name.get(channel, "Firefox Nightly.app"),
+                                             "Contents", "MacOS"))
 
         return binary
 
@@ -328,15 +330,15 @@
                         os.path.expanduser("~/Applications/Firefox Developer Edition.app/Contents/MacOS"),
                         "/Applications/Firefox.app/Contents/MacOS",
                         os.path.expanduser("~/Applications/Firefox.app/Contents/MacOS")]
-            return find_executable("firefox", os.pathsep.join(macpaths))
+            return which("firefox", path=os.pathsep.join(macpaths))
 
         if binary is None:
-            return find_executable("firefox")
+            return which("firefox")
 
         return binary
 
     def find_certutil(self):
-        path = find_executable("certutil")
+        path = which("certutil")
         if path is None:
             return None
         if os.path.splitdrive(os.path.normcase(path))[1].split(os.path.sep) == ["", "windows", "system32", "certutil.exe"]:
@@ -344,7 +346,7 @@
         return path
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("geckodriver")
+        return which("geckodriver")
 
     def get_version_and_channel(self, binary):
         version_string = call(binary, "--version").strip()
@@ -468,7 +470,7 @@
                 unzip(get(url).raw, dest=dest)
             else:
                 untar(get(url).raw, dest=dest)
-            path = find_executable(os.path.join(dest, "geckodriver"))
+            path = which("geckodriver", path=dest)
 
         assert path is not None
         self.logger.info("Installed %s" %
@@ -642,7 +644,7 @@
         # There may be an existing chromedriver binary from a previous install.
         # To provide a clean install experience, remove the old binary - this
         # avoids tricky issues like unzipping over a read-only file.
-        existing_chromedriver_path = find_executable("chromedriver", path)
+        existing_chromedriver_path = which("chromedriver", path=path)
         if existing_chromedriver_path:
             self.logger.info(f"Removing existing ChromeDriver binary: {existing_chromedriver_path}")
             os.chmod(existing_chromedriver_path, stat.S_IWUSR)
@@ -677,7 +679,7 @@
     def find_webdriver(self, venv_path=None, channel=None, browser_binary=None):
         if venv_path:
             venv_path = os.path.join(venv_path, self.product)
-        return find_executable("chromedriver", path=venv_path)
+        return which("chromedriver", path=venv_path)
 
     def install_mojojs(self, dest, browser_binary):
         """Install MojoJS web framework."""
@@ -742,12 +744,12 @@
         # We want to make sure the binary always ends up directly in bin/.
         chromedriver_dir = os.path.join(dest,
                                         f"chromedriver_{self._chromedriver_platform_string}")
-        chromedriver_path = find_executable("chromedriver", chromedriver_dir)
+        chromedriver_path = which("chromedriver", path=chromedriver_dir)
         if chromedriver_path is not None:
             shutil.move(chromedriver_path, dest)
             rmtree(chromedriver_dir)
 
-        chromedriver_path = find_executable("chromedriver", dest)
+        chromedriver_path = which("chromedriver", path=dest)
         assert chromedriver_path is not None
         return chromedriver_path
 
@@ -819,13 +821,13 @@
     def _find_binary_in_directory(self, directory):
         """Search for Chromium browser binary in a given directory."""
         if uname[0] == "Darwin":
-            return find_executable("Chromium", os.path.join(directory,
-                                                            self._chromium_package_name,
-                                                            "Chromium.app",
-                                                            "Contents",
-                                                            "MacOS"))
-        # find_executable will add .exe on Windows automatically.
-        return find_executable("chrome", os.path.join(directory, self._chromium_package_name))
+            return which("Chromium", path=os.path.join(directory,
+                                                       self._chromium_package_name,
+                                                       "Chromium.app",
+                                                       "Contents",
+                                                       "MacOS"))
+        # which will add .exe on Windows automatically.
+        return which("chrome", path=os.path.join(directory, self._chromium_package_name))
 
     def _get_webdriver_url(self, version, revision=None):
         """Get Chromium Snapshots url to download Chromium ChromeDriver."""
@@ -980,7 +982,7 @@
             elif channel == "dev":
                 name += "-unstable"
             # No Canary on Linux.
-            return find_executable(name)
+            return which(name)
         if uname[0] == "Darwin":
             suffix = ""
             if channel in ("beta", "dev", "canary"):
@@ -1078,8 +1080,8 @@
 
     def find_binary(self, venv_path=None, channel=None):
         if uname[0] == "Darwin":
-            return find_executable("Content Shell.app/Contents/MacOS/Content Shell")
-        return find_executable("content_shell")  # .exe is added automatically for Windows
+            return which("Content Shell.app/Contents/MacOS/Content Shell")
+        return which("content_shell")  # .exe is added automatically for Windows
 
     def find_webdriver(self, venv_path=None, channel=None):
         return None
@@ -1114,7 +1116,7 @@
         raise NotImplementedError
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("chromedriver")
+        return which("chromedriver")
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         if browser_binary is None:
@@ -1276,7 +1278,7 @@
         raise NotImplementedError
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("operadriver")
+        return which("operadriver")
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         if dest is None:
@@ -1290,7 +1292,7 @@
         shutil.move(os.path.join(operadriver_dir, "operadriver"), dest)
         rmtree(operadriver_dir)
 
-        path = find_executable("operadriver")
+        path = which("operadriver")
         st = os.stat(path)
         os.chmod(path, st.st_mode | stat.S_IEXEC)
         return path
@@ -1337,7 +1339,7 @@
             elif channel == "dev":
                 name += "-dev"
             # No Canary on Linux.
-            return find_executable(name)
+            return which(name)
         if self.platform == "macos":
             suffix = ""
             if channel in ("beta", "dev", "canary"):
@@ -1348,25 +1350,25 @@
             if channel == "beta":
                 winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Microsoft\\Edge Beta\\Application"),
                             os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Microsoft\\Edge Beta\\Application")]
-                return find_executable(binaryname, os.pathsep.join(winpaths))
+                return which(binaryname, path=os.pathsep.join(winpaths))
             elif channel == "dev":
                 winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Microsoft\\Edge Dev\\Application"),
                             os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Microsoft\\Edge Dev\\Application")]
-                return find_executable(binaryname, os.pathsep.join(winpaths))
+                return which(binaryname, path=os.pathsep.join(winpaths))
             elif channel == "canary":
                 winpaths = [os.path.expanduser("~\\AppData\\Local\\Microsoft\\Edge\\Application"),
                             os.path.expanduser("~\\AppData\\Local\\Microsoft\\Edge SxS\\Application")]
-                return find_executable(binaryname, os.pathsep.join(winpaths))
+                return which(binaryname, path=os.pathsep.join(winpaths))
             else:
                 winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Microsoft\\Edge\\Application"),
                             os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Microsoft\\Edge\\Application")]
-                return find_executable(binaryname, os.pathsep.join(winpaths))
+                return which(binaryname, path=os.pathsep.join(winpaths))
 
         self.logger.warning("Unable to find the browser binary.")
         return None
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("msedgedriver")
+        return which("msedgedriver")
 
     def webdriver_supports_browser(self, webdriver_binary, browser_binary):
         edgedriver_version = self.webdriver_version(webdriver_binary)
@@ -1420,7 +1422,7 @@
         unzip(get(url).raw, dest)
         if os.path.isfile(edgedriver_path):
             self.logger.info(f"Successfully downloaded MSEdgeDriver to {edgedriver_path}")
-        return find_executable(self.edgedriver_name, dest)
+        return which(self.edgedriver_name, path=dest)
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         self.logger.info(f"Installing MSEdgeDriver for channel {channel}")
@@ -1503,7 +1505,7 @@
         raise NotImplementedError
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("MicrosoftWebDriver")
+        return which("MicrosoftWebDriver")
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         raise NotImplementedError
@@ -1537,7 +1539,7 @@
         raise NotImplementedError
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("IEDriverServer.exe")
+        return which("IEDriverServer.exe")
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         raise NotImplementedError
@@ -1758,7 +1760,7 @@
         path = None
         if channel == "preview":
             path = "/Applications/Safari Technology Preview.app/Contents/MacOS"
-        return find_executable("safaridriver", path)
+        return which("safaridriver", path=path)
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         raise NotImplementedError
@@ -1835,15 +1837,15 @@
 
         resp = self._get(channel)
         decompress(resp.raw, dest=dest)
-        path = find_executable("servo", os.path.join(dest, "servo"))
+        path = which("servo", path=os.path.join(dest, "servo"))
         st = os.stat(path)
         os.chmod(path, st.st_mode | stat.S_IEXEC)
         return path
 
     def find_binary(self, venv_path=None, channel=None):
-        path = find_executable("servo", os.path.join(venv_path, "servo"))
+        path = which("servo", path=os.path.join(venv_path, "servo"))
         if path is None:
-            path = find_executable("servo")
+            path = which("servo")
         return path
 
     def find_webdriver(self, venv_path=None, channel=None):
@@ -2010,7 +2012,7 @@
 
     def find_binary(self, venv_path=None, channel="main"):
         path = self._get_browser_binary_dir(venv_path, channel)
-        return find_executable("WebKitTestRunner", os.path.join(path, "Release"))
+        return which("WebKitTestRunner", path=os.path.join(path, "Release"))
 
     def find_webdriver(self, venv_path=None, channel="main"):
         return None
@@ -2026,8 +2028,6 @@
 
 
 class WebKitGTKMiniBrowser(WebKit):
-
-
     def _get_osidversion(self):
         with open('/etc/os-release') as osrelease_handle:
             for line in osrelease_handle.readlines():
@@ -2035,11 +2035,11 @@
                     os_id = line.split('=')[1].strip().strip('"')
                 if line.startswith('VERSION_ID='):
                     version_id = line.split('=')[1].strip().strip('"')
-        assert(os_id)
-        assert(version_id)
+        assert os_id
+        assert version_id
         osidversion = os_id + '-' + version_id
-        assert(' ' not in osidversion)
-        assert(len(osidversion) > 3)
+        assert ' ' not in osidversion
+        assert len(osidversion) > 3
         return osidversion.capitalize()
 
 
@@ -2113,10 +2113,9 @@
             bundle_dir = os.path.join(venv_base_path, "webkitgtk_minibrowser")
             install_ok_file = os.path.join(bundle_dir, ".installation-ok")
             if os.path.isfile(install_ok_file):
-                return find_executable(binary, bundle_dir)
+                return which(binary, path=bundle_dir)
         return None
 
-
     def find_binary(self, venv_path=None, channel=None):
         minibrowser_path = self._find_executable_in_channel_bundle("MiniBrowser", venv_path, channel)
         if minibrowser_path:
@@ -2125,7 +2124,7 @@
         libexecpaths = ["/usr/libexec/webkit2gtk-4.0"]  # Fedora path
         triplet = "x86_64-linux-gnu"
         # Try to use GCC to detect this machine triplet
-        gcc = find_executable("gcc")
+        gcc = which("gcc")
         if gcc:
             try:
                 triplet = call(gcc, "-dumpmachine").strip()
@@ -2133,12 +2132,12 @@
                 pass
         # Add Debian/Ubuntu path
         libexecpaths.append("/usr/lib/%s/webkit2gtk-4.0" % triplet)
-        return find_executable("MiniBrowser", os.pathsep.join(libexecpaths))
+        return which("MiniBrowser", path=os.pathsep.join(libexecpaths))
 
     def find_webdriver(self, venv_path=None, channel=None):
         webdriver_path = self._find_executable_in_channel_bundle("WebKitWebDriver", venv_path, channel)
         if not webdriver_path:
-            webdriver_path = find_executable("WebKitWebDriver")
+            webdriver_path = which("WebKitWebDriver")
         return webdriver_path
 
     def version(self, binary=None, webdriver_binary=None):
@@ -2171,10 +2170,10 @@
         raise NotImplementedError
 
     def find_binary(self, venv_path=None, channel=None):
-        return find_executable("epiphany")
+        return which("epiphany")
 
     def find_webdriver(self, venv_path=None, channel=None):
-        return find_executable("WebKitWebDriver")
+        return which("WebKitWebDriver")
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         raise NotImplementedError
diff --git a/third_party/wpt_tools/wpt/tools/wpt/run.py b/third_party/wpt_tools/wpt/tools/wpt/run.py
index 72ceab08..9a1111c 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/run.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/run.py
@@ -4,7 +4,7 @@
 import os
 import platform
 import sys
-from distutils.spawn import find_executable
+from shutil import which
 from typing import ClassVar, Tuple, Type
 
 wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
@@ -92,7 +92,7 @@
         if kwargs["host_cert_path"] is None:
             kwargs["host_cert_path"] = os.path.join(cert_root, "web-platform.test.pem")
     elif kwargs["ssl_type"] == "openssl":
-        if not find_executable(kwargs["openssl_binary"]):
+        if not which(kwargs["openssl_binary"]):
             if os.uname()[0] == "Windows":
                 raise WptrunError("""OpenSSL binary not found. If you need HTTPS tests, install OpenSSL from
 
@@ -158,8 +158,8 @@
 
 
 class BrowserSetup:
-    name = None  # type: ClassVar[str]
-    browser_cls = None  # type: ClassVar[Type[browser.Browser]]
+    name: ClassVar[str]
+    browser_cls: ClassVar[Type[browser.Browser]]
 
     def __init__(self, venv, prompt=True):
         self.browser = self.browser_cls(logger)
@@ -335,8 +335,8 @@
 
 class Chrome(BrowserSetup):
     name = "chrome"
-    browser_cls = browser.Chrome  # type: ClassVar[Type[browser.ChromeChromiumBase]]
-    experimental_channels = ("dev", "canary", "nightly")  # type: ClassVar[Tuple[str, ...]]
+    browser_cls: ClassVar[Type[browser.ChromeChromiumBase]] = browser.Chrome
+    experimental_channels: ClassVar[Tuple[str, ...]] = ("dev", "canary", "nightly")
 
     def setup_kwargs(self, kwargs):
         browser_channel = kwargs["browser_channel"]
@@ -429,7 +429,7 @@
 
 class Chromium(Chrome):
     name = "chromium"
-    browser_cls = browser.Chromium  # type: ClassVar[Type[browser.ChromeChromiumBase]]
+    browser_cls: ClassVar[Type[browser.ChromeChromiumBase]] = browser.Chromium
     experimental_channels = ("nightly",)
 
 
diff --git a/third_party/wpt_tools/wpt/tools/wpt/testfiles.py b/third_party/wpt_tools/wpt/tools/wpt/testfiles.py
index 74c97ce..e13b878 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/testfiles.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/testfiles.py
@@ -16,24 +16,12 @@
     # reference
     #
     # note we need both because depending on caller we may/may not have the
-    # paths set up correctly to handle both and MYPY has no knowledge of our
+    # paths set up correctly to handle both and mypy has no knowledge of our
     # sys.path magic
     from manifest import manifest  # type: ignore
     from manifest.utils import git as get_git_cmd  # type: ignore
 
-MYPY = False
-if MYPY:
-    # MYPY is set to True when run under Mypy.
-    from typing import Any
-    from typing import Dict
-    from typing import Iterable
-    from typing import List
-    from typing import Optional
-    from typing import Pattern
-    from typing import Sequence
-    from typing import Set
-    from typing import Text
-    from typing import Tuple
+from typing import Any, Dict, Iterable, List, Optional, Pattern, Sequence, Set, Text, Tuple
 
 DEFAULT_IGNORE_RULES = ("resources/testharness*", "resources/testdriver*")
 
@@ -43,13 +31,11 @@
 logger = logging.getLogger()
 
 
-def display_branch_point():
-    # type: () -> None
+def display_branch_point() -> None:
     print(branch_point())
 
 
-def branch_point():
-    # type: () -> Optional[Text]
+def branch_point() -> Optional[Text]:
     git = get_git_cmd(wpt_root)
     if git is None:
         raise Exception("git not found")
@@ -62,7 +48,7 @@
         # This is a PR, so the base branch is in GITHUB_BRANCH
         base_branch = os.environ.get("GITHUB_BRANCH")
         assert base_branch, "GITHUB_BRANCH environment variable is defined"
-        branch_point = git("merge-base", "HEAD", base_branch)  # type: Optional[Text]
+        branch_point: Optional[Text] = git("merge-base", "HEAD", base_branch)
     else:
         # Otherwise we aren't on a PR, so we try to find commits that are only in the
         # current branch c.f.
@@ -87,7 +73,7 @@
                                                 cmd,
                                                 commits_bytes)
 
-        commit_parents = OrderedDict()  # type: Dict[Text, List[Text]]
+        commit_parents: Dict[Text, List[Text]] = OrderedDict()
         commits = commits_bytes.decode("ascii")
         if commits:
             for line in commits.split("\n"):
@@ -133,8 +119,7 @@
     return branch_point
 
 
-def compile_ignore_rule(rule):
-    # type: (Text) -> Pattern[Text]
+def compile_ignore_rule(rule: Text) -> Pattern[Text]:
     rule = rule.replace(os.path.sep, "/")
     parts = rule.split("/")
     re_parts = []
@@ -148,8 +133,7 @@
     return re.compile("^%s$" % "/".join(re_parts))
 
 
-def repo_files_changed(revish, include_uncommitted=False, include_new=False):
-    # type: (Text, bool, bool) -> Set[Text]
+def repo_files_changed(revish: Text, include_uncommitted: bool = False, include_new: bool = False) -> Set[Text]:
     git = get_git_cmd(wpt_root)
     if git is None:
         raise Exception("git not found")
@@ -186,8 +170,7 @@
     return files
 
 
-def exclude_ignored(files, ignore_rules):
-    # type: (Iterable[Text], Optional[Sequence[Text]]) -> Tuple[List[Text], List[Text]]
+def exclude_ignored(files: Iterable[Text], ignore_rules: Optional[Sequence[Text]]) -> Tuple[List[Text], List[Text]]:
     if ignore_rules is None:
         ignore_rules = DEFAULT_IGNORE_RULES
     compiled_ignore_rules = [compile_ignore_rule(item) for item in set(ignore_rules)]
@@ -207,12 +190,11 @@
     return changed, ignored
 
 
-def files_changed(revish,  # type: Text
-                  ignore_rules=None,  # type: Optional[Sequence[Text]]
-                  include_uncommitted=False,  # type: bool
-                  include_new=False  # type: bool
-                  ):
-    # type: (...) -> Tuple[List[Text], List[Text]]
+def files_changed(revish: Text,
+                  ignore_rules: Optional[Sequence[Text]] = None,
+                  include_uncommitted: bool = False,
+                  include_new: bool = False
+                  ) -> Tuple[List[Text], List[Text]]:
     """Find files changed in certain revisions.
 
     The function passes `revish` directly to `git diff`, so `revish` can have a
@@ -228,27 +210,24 @@
     return exclude_ignored(files, ignore_rules)
 
 
-def _in_repo_root(full_path):
-    # type: (Text) -> bool
+def _in_repo_root(full_path: Text) -> bool:
     rel_path = os.path.relpath(full_path, wpt_root)
     path_components = rel_path.split(os.sep)
     return len(path_components) < 2
 
 
-def load_manifest(manifest_path=None, manifest_update=True):
-    # type: (Optional[Text], bool) -> manifest.Manifest
+def load_manifest(manifest_path: Optional[Text] = None, manifest_update: bool = True) -> manifest.Manifest:
     if manifest_path is None:
         manifest_path = os.path.join(wpt_root, "MANIFEST.json")
     return manifest.load_and_update(wpt_root, manifest_path, "/",
                                     update=manifest_update)
 
 
-def affected_testfiles(files_changed,  # type: Iterable[Text]
-                       skip_dirs=None,  # type: Optional[Set[Text]]
-                       manifest_path=None,  # type: Optional[Text]
-                       manifest_update=True  # type: bool
-                       ):
-    # type: (...) -> Tuple[Set[Text], Set[Text]]
+def affected_testfiles(files_changed: Iterable[Text],
+                       skip_dirs: Optional[Set[Text]] = None,
+                       manifest_path: Optional[Text] = None,
+                       manifest_update: bool = True
+                       ) -> Tuple[Set[Text], Set[Text]]:
     """Determine and return list of test files that reference changed files."""
     if skip_dirs is None:
         skip_dirs = {"conformance-checkers", "docs", "tools"}
@@ -277,7 +256,7 @@
     tests_changed = {item for item in files_changed if item in test_files}
 
     nontest_changed_paths = set()
-    rewrites = {"/resources/webidl2/lib/webidl2.js": "/resources/WebIDLParser.js"}  # type: Dict[Text, Text]
+    rewrites: Dict[Text, Text] = {"/resources/webidl2/lib/webidl2.js": "/resources/WebIDLParser.js"}
     for full_path in nontests_changed:
         rel_path = os.path.relpath(full_path, wpt_root)
         path_components = rel_path.split(os.sep)
@@ -293,8 +272,7 @@
     interfaces_changed_names = [os.path.splitext(os.path.basename(interface))[0]
                                 for interface in interfaces_changed]
 
-    def affected_by_wdspec(test):
-        # type: (Text) -> bool
+    def affected_by_wdspec(test: Text) -> bool:
         affected = False
         if test in wdspec_test_files:
             for support_full_path, _ in nontest_changed_paths:
@@ -309,8 +287,7 @@
                     break
         return affected
 
-    def affected_by_interfaces(file_contents):
-        # type: (Text) -> bool
+    def affected_by_interfaces(file_contents: Text) -> bool:
         if len(interfaces_changed_names) > 0:
             if 'idlharness.js' in file_contents:
                 for interface in interfaces_changed_names:
@@ -335,9 +312,9 @@
                 continue
 
             with open(test_full_path, "rb") as fh:
-                raw_file_contents = fh.read()  # type: bytes
+                raw_file_contents: bytes = fh.read()
                 if raw_file_contents.startswith(b"\xfe\xff"):
-                    file_contents = raw_file_contents.decode("utf-16be", "replace")  # type: Text
+                    file_contents: Text = raw_file_contents.decode("utf-16be", "replace")
                 elif raw_file_contents.startswith(b"\xff\xfe"):
                     file_contents = raw_file_contents.decode("utf-16le", "replace")
                 else:
@@ -351,8 +328,7 @@
     return tests_changed, affected_testfiles
 
 
-def get_parser():
-    # type: () -> argparse.ArgumentParser
+def get_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser()
     parser.add_argument("revish", default=None, help="Commits to consider. Defaults to the "
                         "commits on the current branch", nargs="?")
@@ -375,8 +351,7 @@
     return parser
 
 
-def get_parser_affected():
-    # type: () -> argparse.ArgumentParser
+def get_parser_affected() -> argparse.ArgumentParser:
     parser = get_parser()
     parser.add_argument("--metadata",
                         dest="metadata_root",
@@ -386,16 +361,14 @@
     return parser
 
 
-def get_revish(**kwargs):
-    # type: (**Any) -> Text
+def get_revish(**kwargs: Any) -> Text:
     revish = kwargs.get("revish")
     if revish is None:
         revish = "%s..HEAD" % branch_point()
     return revish.strip()
 
 
-def run_changed_files(**kwargs):
-    # type: (**Any) -> None
+def run_changed_files(**kwargs: Any) -> None:
     revish = get_revish(**kwargs)
     changed, _ = files_changed(revish,
                                kwargs["ignore_rule"],
@@ -409,8 +382,7 @@
         sys.stdout.write(line)
 
 
-def run_tests_affected(**kwargs):
-    # type: (**Any) -> None
+def run_tests_affected(**kwargs: Any) -> None:
     revish = get_revish(**kwargs)
     changed, _ = files_changed(revish,
                                kwargs["ignore_rule"],
diff --git a/third_party/wpt_tools/wpt/tools/wpt/virtualenv.py b/third_party/wpt_tools/wpt/tools/wpt/virtualenv.py
index 05ca522..7176baf 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/virtualenv.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/virtualenv.py
@@ -4,7 +4,7 @@
 import shutil
 import sys
 import logging
-from distutils.spawn import find_executable
+from shutil import which
 
 # The `pkg_resources` module is provided by `setuptools`, which is itself a
 # dependency of `virtualenv`. Tolerate its absence so that this module may be
@@ -27,7 +27,7 @@
         self.path = path
         self.skip_virtualenv_setup = skip_virtualenv_setup
         if not skip_virtualenv_setup:
-            self.virtualenv = find_executable("virtualenv")
+            self.virtualenv = which("virtualenv")
             if not self.virtualenv:
                 raise ValueError("virtualenv must be installed and on the PATH")
             self._working_set = None
@@ -57,7 +57,7 @@
 
     @property
     def pip_path(self):
-        path = find_executable("pip3", self.bin_path)
+        path = which("pip3", path=self.bin_path)
         if path is None:
             raise ValueError("pip3 not found")
         return path
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt
index c55bc63..fcaab2e 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt
@@ -3,7 +3,8 @@
 mozinfo==1.2.2  # https://bugzilla.mozilla.org/show_bug.cgi?id=1621226
 mozlog==7.1.1
 mozprocess==1.3.0
+packaging==23.1
 pillow==9.5.0
-requests==2.29.0
+requests==2.30.0
 six==1.16.0
-urllib3[secure]==1.26.15
+urllib3==1.26.15
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt
index 0650e66..9dd3989 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt
@@ -1,2 +1,2 @@
 selenium==4.9.0
-requests==2.29.0
+requests==2.30.0
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/tox.ini b/third_party/wpt_tools/wpt/tools/wptrunner/tox.ini
index 82df778..5aef267 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/tox.ini
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/tox.ini
@@ -17,7 +17,7 @@
      safari: -r{toxinidir}/requirements_safari.txt
      sauce: -r{toxinidir}/requirements_sauce.txt
 
-commands = pytest {posargs}
+commands = pytest -c{toxinidir}/../pytest.ini --rootdir={toxinidir} {posargs}
 
 setenv = CURRENT_TOX_ENV = {envname}
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
index 3e4a6c8..f7dcd55 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
@@ -2,8 +2,8 @@
 
 import os
 import plistlib
-from distutils.spawn import find_executable
-from distutils.version import LooseVersion
+from packaging.version import Version
+from shutil import which
 
 import psutil
 
@@ -50,9 +50,9 @@
     if kwargs["binary"] is not None:
         raise ValueError("Safari doesn't support setting executable location")
 
-    V = LooseVersion
     browser_bundle_version = run_info_data["browser_bundle_version"]
-    if browser_bundle_version is not None and V(browser_bundle_version[2:]) >= V("613.1.7.1"):
+    if (browser_bundle_version is not None and
+        Version(browser_bundle_version[2:]) >= Version("613.1.7.1")):
         logger.debug("using acceptInsecureCerts=True")
         executor_kwargs["capabilities"]["acceptInsecureCerts"] = True
     else:
@@ -161,7 +161,7 @@
                          env=env)
 
         if "/" not in webdriver_binary:
-            wd_path = find_executable(webdriver_binary)
+            wd_path = which(webdriver_binary)
         else:
             wd_path = webdriver_binary
         self.safari_path = self._find_safari_executable(wd_path)
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
index f62ded6..3159bb3 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
@@ -245,14 +245,14 @@
     """
     __metaclass__ = ABCMeta
 
-    test_type = None  # type: ClassVar[str]
+    test_type: ClassVar[str]
     # convert_result is a class variable set to a callable converter
     # (e.g. reftest_result_converter) converting from an instance of
     # URLManifestItem (e.g. RefTest) + type-dependent results object +
     # type-dependent extra data, returning a tuple of Result and list of
     # SubtestResult. For now, any callable is accepted. TODO: Make this type
     # stricter when more of the surrounding code is annotated.
-    convert_result = None  # type: ClassVar[Callable[..., Any]]
+    convert_result: ClassVar[Callable[..., Any]]
     supports_testdriver = False
     supports_jsshell = False
     # Extra timeout to use after internal test timeout at which the harness
@@ -605,7 +605,7 @@
 
 class WdspecExecutor(TestExecutor):
     convert_result = pytest_result_converter
-    protocol_cls = WdspecProtocol  # type: ClassVar[Type[Protocol]]
+    protocol_cls: ClassVar[Type[Protocol]] = WdspecProtocol
 
     def __init__(self, logger, browser, server_config, webdriver_binary,
                  webdriver_args, timeout_multiplier=1, capabilities=None,
@@ -702,7 +702,7 @@
     WebDriver. Things that are more different to WebDriver may need to create a
     fully custom implementation."""
 
-    unimplemented_exc = (NotImplementedError,)  # type: ClassVar[Tuple[Type[Exception], ...]]
+    unimplemented_exc: ClassVar[Tuple[Type[Exception], ...]] = (NotImplementedError,)
 
     def __init__(self, logger, protocol, test_window):
         self.protocol = protocol
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
index af64cc8..4cef013 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
@@ -32,7 +32,7 @@
     :param Browser browser: The Browser using this protocol"""
     __metaclass__ = ABCMeta
 
-    implements = []  # type: ClassVar[List[Type[ProtocolPart]]]
+    implements: ClassVar[List[Type["ProtocolPart"]]] = []
 
     def __init__(self, executor, browser):
         self.executor = executor
@@ -98,7 +98,7 @@
     :param Protocol parent: The parent protocol"""
     __metaclass__ = ABCMeta
 
-    name = None  # type: ClassVar[str]
+    name: ClassVar[str]
 
     def __init__(self, parent):
         self.parent = parent
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py
index 0bc2518..00889717 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py
@@ -4,7 +4,7 @@
 from urllib.parse import urljoin, urlsplit
 from collections import namedtuple, defaultdict, deque
 from math import ceil
-from typing import Any, Callable, ClassVar, Dict, List
+from typing import Any, Callable, ClassVar, Dict, List, Optional
 
 from .wptmanifest import serialize
 from .wptmanifest.node import (DataNode, ConditionalNode, BinaryExpressionNode,
@@ -336,13 +336,13 @@
 
 
 class PropertyUpdate:
-    property_name = None  # type: ClassVar[str]
-    cls_default_value = None  # type: ClassVar[Any]
-    value_type = None  # type: ClassVar[type]
+    property_name: ClassVar[str]
+    cls_default_value: ClassVar[Optional[Any]] = None
+    value_type: ClassVar[Optional[type]] = None
     # property_builder is a class variable set to either build_conditional_tree
     # or build_unconditional_tree. TODO: Make this type stricter when those
     # methods are annotated.
-    property_builder = None  # type: ClassVar[Callable[..., Any]]
+    property_builder: ClassVar[Callable[..., Any]]
 
     def __init__(self, node):
         self.node = node
@@ -784,7 +784,7 @@
 
 
 class AppendOnlyListUpdate(PropertyUpdate):
-    cls_default_value = []  # type: ClassVar[List[str]]
+    cls_default_value: ClassVar[List[str]] = []
     property_builder = build_unconditional_tree
 
     def updated_value(self, current, new):
@@ -837,7 +837,7 @@
 
 class LeakThresholdUpdate(PropertyUpdate):
     property_name = "leak-threshold"
-    cls_default_value = {}  # type: ClassVar[Dict[str, int]]
+    cls_default_value: ClassVar[Dict[str, int]] = {}
     property_builder = build_unconditional_tree
 
     def from_result_value(self, result):
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/base.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/base.py
index bd39e23..22ccde7 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/base.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/base.py
@@ -7,7 +7,7 @@
 
 
 class Step:
-    provides = []  # type: ClassVar[List[str]]
+    provides: ClassVar[List[str]] = []
 
     def __init__(self, logger):
         self.logger = logger
@@ -49,7 +49,7 @@
 
 
 class StepRunner:
-    steps = []  # type: ClassVar[List[Type[Step]]]
+    steps: ClassVar[List[Type[Step]]] = []
 
     def __init__(self, logger, state):
         """Class that runs a specified series of Steps with a common State"""
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
index d3b939b..416e75b 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
@@ -4,7 +4,7 @@
 import os
 import sys
 from collections import OrderedDict
-from distutils.spawn import find_executable
+from shutil import which
 from datetime import timedelta
 
 from . import config
@@ -507,7 +507,7 @@
     if name is None:
         return
 
-    path = find_executable(name)
+    path = which(name)
     if path and os.access(path, os.X_OK):
         return path
     else:
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py
index 7d4f257..695d6a3 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py
@@ -33,7 +33,7 @@
         if isinstance(self.value_node, ValueNode):
             self.value_node.data = value
         else:
-            assert(isinstance(self.value_node, ListNode))
+            assert isinstance(self.value_node, ListNode)
             while self.value_node.children:
                 self.value_node.children[0].remove()
             assert len(self.value_node.children) == 0
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wpttest.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wpttest.py
index 65577c1..492549d 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wpttest.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wpttest.py
@@ -1,10 +1,10 @@
 # mypy: allow-untyped-defs
-
 import os
 import subprocess
 import sys
+from abc import ABC
 from collections import defaultdict
-from typing import Any, ClassVar, Dict, Type
+from typing import Any, ClassVar, Dict, Optional, Type
 from urllib.parse import urljoin
 
 from .wptmanifest.parser import atoms
@@ -13,7 +13,7 @@
 enabled_tests = {"testharness", "reftest", "wdspec", "crashtest", "print-reftest"}
 
 
-class Result:
+class Result(ABC):
     def __init__(self,
                  status,
                  message,
@@ -34,7 +34,7 @@
         return f"<{self.__module__}.{self.__class__.__name__} {self.status}>"
 
 
-class SubtestResult:
+class SubtestResult(ABC):
     def __init__(self, name, status, message, stack=None, expected=None, known_intermittent=None):
         self.name = name
         if status not in self.statuses:
@@ -203,11 +203,10 @@
     return "http"
 
 
-class Test:
-
-    result_cls = None  # type: ClassVar[Type[Result]]
-    subtest_result_cls = None  # type: ClassVar[Type[SubtestResult]]
-    test_type = None  # type: ClassVar[str]
+class Test(ABC):
+    result_cls: ClassVar[Type[Result]]
+    subtest_result_cls: ClassVar[Optional[Type[SubtestResult]]] = None
+    test_type: ClassVar[str]
     pac = None
 
     default_timeout = 10  # seconds
@@ -484,14 +483,6 @@
         return self.url
 
 
-class ManualTest(Test):
-    test_type = "manual"
-
-    @property
-    def id(self):
-        return self.url
-
-
 class ReftestTest(Test):
     """A reftest
 
@@ -704,7 +695,6 @@
 manifest_test_cls = {"reftest": ReftestTest,
                      "print-reftest": PrintReftestTest,
                      "testharness": TestharnessTest,
-                     "manual": ManualTest,
                      "wdspec": WdspecTest,
                      "crashtest": CrashTest}
 
diff --git a/third_party/wpt_tools/wpt/tools/wptserve/wptserve/pipes.py b/third_party/wpt_tools/wpt/tools/wptserve/wptserve/pipes.py
index a9a85a1..84b17c1 100644
--- a/third_party/wpt_tools/wpt/tools/wptserve/wptserve/pipes.py
+++ b/third_party/wpt_tools/wpt/tools/wptserve/wptserve/pipes.py
@@ -21,7 +21,7 @@
 
 
 class Pipeline:
-    pipes = {}  # type: ClassVar[Dict[str, Callable[..., Any]]]
+    pipes: ClassVar[Dict[str, Callable[..., Any]]] = {}
 
     def __init__(self, pipe_string):
         self.pipe_functions = self.parse(pipe_string)
diff --git a/third_party/xdg_shared_mime_info/README.chromium b/third_party/xdg_shared_mime_info/README.chromium
index 6ea40661..767ffac 100644
--- a/third_party/xdg_shared_mime_info/README.chromium
+++ b/third_party/xdg_shared_mime_info/README.chromium
@@ -2,18 +2,18 @@
 Short Name: xdg-shared-mime-info
 URL: http://freedesktop.org
 Version: 0
-Date: 2022/08/23
+Date: 2023/05/11
 License: GPL v2
 License File: LICENSE
 Security Critical: no
 
 Description:
 File freedesktop.org.xml.in synced from
-https://github.com/freedesktop/xdg-shared-mime-info
-f4e7cbc86e67e7bc39cf8167823fcf0d8ace9ce1 on 2022/08/23
+https://gitlab.freedesktop.org/xdg/shared-mime-info
+ff51a0a9e36185288576ca32da9a8fa4bd2e6e90 on 2023/05/11
 
 generate.py will fetch the latest version of the xml file from the freedesktop
-repo and save it to this dir, and generate mime_cache.cc.
+repo and save it to this dir, and generate mime_cache.gen.cc.
 
 This script can be run from time to time as the freedesktop database is updated.
 About once a year or so would be fine since the database does not change often.
diff --git a/third_party/xdg_shared_mime_info/freedesktop.org.xml.in b/third_party/xdg_shared_mime_info/freedesktop.org.xml.in
index 03f8ae1..3f1821a 100644
--- a/third_party/xdg_shared_mime_info/freedesktop.org.xml.in
+++ b/third_party/xdg_shared_mime_info/freedesktop.org.xml.in
@@ -269,8 +269,6 @@
   </mime-type>
   <mime-type type="application/pgp-encrypted">
     <comment>PGP/MIME-encrypted message header</comment>
-    <sub-class-of type="text/plain"/>
-    <generic-icon name="text-x-generic"/>
     <magic>
       <match type="string" value="-----BEGIN PGP MESSAGE-----" offset="0"/>
     </magic>
@@ -283,8 +281,6 @@
     <comment>PGP keys</comment>
     <acronym>PGP</acronym>
     <expanded-acronym>Pretty Good Privacy</expanded-acronym>
-    <sub-class-of type="text/plain"/>
-    <generic-icon name="text-x-generic"/>
     <magic>
       <match type="string" value="-----BEGIN PGP PUBLIC KEY BLOCK-----" offset="0"/>
       <match type="string" value="-----BEGIN PGP PRIVATE KEY BLOCK-----" offset="0"/>
@@ -302,8 +298,6 @@
   </mime-type>
   <mime-type type="application/pgp-signature">
     <comment>detached OpenPGP signature</comment>
-    <sub-class-of type="text/plain"/>
-    <generic-icon name="text-x-generic"/>
     <magic>
       <match type="string" value="-----BEGIN PGP SIGNATURE-----" offset="0"/>
     </magic>
@@ -1341,7 +1335,7 @@
   </mime-type>
   <mime-type type="application/vnd.android.package-archive">
     <comment>Android package</comment>
-    <sub-class-of type="application/x-java-archive"/>
+    <sub-class-of type="application/java-archive"/>
     <glob pattern="*.apk"/>
   </mime-type>
   <mime-type type="application/vnd.symbian.install">
@@ -2533,12 +2527,12 @@
       <match type="string" value="S T O P" offset="0"/>
     </magic>
   </mime-type>
-  <mime-type type="application/x-java-archive">
+  <mime-type type="application/java-archive">
     <comment>Java archive</comment>
     <sub-class-of type="application/zip"/>
     <generic-icon name="package-x-generic"/>
     <alias type="application/x-jar"/>
-    <alias type="application/java-archive"/>
+    <alias type="application/x-java-archive"/>
     <glob pattern="*.jar"/>
   </mime-type>
   <mime-type type="application/x-java">
@@ -3704,6 +3698,11 @@
     <glob pattern="*.cr"/>
     <alias type="text/crystal"/>
   </mime-type>
+  <mime-type type="text/julia">
+    <comment>Julia source code</comment>
+    <sub-class-of type="text/plain"/>
+    <glob pattern="*.jl"/>
+  </mime-type>  
   <mime-type type="text/rust">
     <comment>Rust source code</comment>
     <sub-class-of type="text/plain"/>
@@ -5873,6 +5872,7 @@
     <comment>glTF model</comment>
     <acronym>glTF</acronym>
     <expanded-acronym>GL Transmission Format</expanded-acronym>
+    <generic-icon name="image-x-generic"/>
     <magic>
       <match type="string" value="glTF" offset="0"/>
     </magic>
@@ -5882,6 +5882,7 @@
     <comment>glTF model</comment>
     <acronym>glTF</acronym>
     <expanded-acronym>GL Transmission Format</expanded-acronym>
+    <generic-icon name="image-x-generic"/>
     <sub-class-of type="application/json"/>
     <glob pattern="*.gltf"/>
   </mime-type>
@@ -5901,6 +5902,7 @@
   <mime-type type="model/obj">
     <comment>OBJ 3D model</comment>
     <sub-class-of type="text/plain"/>
+    <generic-icon name="image-x-generic"/>
     <magic>
       <match type="string" value=" OBJ File: '" offset="0:64"/>
       <match type="string" value="mtllib " offset="0:256"/>
@@ -5909,6 +5911,7 @@
   </mime-type>
   <mime-type type="model/mtl">
     <comment>OBJ 3D model material library</comment>
+    <generic-icon name="image-x-generic"/>
     <sub-class-of type="text/plain"/>
     <magic>
       <match type="string" value="# Blender MTL File: '" offset="0"/>
@@ -7743,12 +7746,14 @@
     <glob pattern="*.3mf"/>
     <alias type="application/vnd.ms-3mfdocument"/>
     <sub-class-of type="application/zip"/>
+    <generic-icon name="image-x-generic"/>
   </mime-type>
 
   <mime-type type="model/stl">
     <comment>STL 3D model</comment>
     <acronym>STL</acronym>
     <expanded-acronym>StereoLithography</expanded-acronym>
+    <generic-icon name="image-x-generic"/>
     <magic>
       <match type="string" value="solid" offset="0"/>
       <match type="string" value="SOLID" offset="0"/>
@@ -7963,6 +7968,14 @@
     <alias type="image/avif-sequence"/>
   </mime-type>
 
+  <mime-type type="image/qoi">
+    <comment>Quite OK Image Format</comment>
+    <magic>
+      <match type="string" value="qoif" offset="0"/>
+    </magic>
+    <glob pattern="*.qoi"/>
+  </mime-type>
+
   <mime-type type="video/vnd.radgamettools.bink">
     <comment>Bink Video</comment>
     <magic>
@@ -8028,4 +8041,11 @@
     <root-XML namespaceURI="http://www.w3.org/2005/sparql-results#" localName="sparql"/>
     <glob pattern="*.srx"/>
   </mime-type>
+  <mime-type type="application/x-openvpn-profile">
+    <comment>OpenVPN profile</comment>
+    <sub-class-of type="text/plain"/>
+    <generic-icon name="text-x-generic"/>
+    <glob pattern="*.openvpn"/>
+    <glob pattern="*.ovpn"/>
+  </mime-type>
 </mime-info>
diff --git a/third_party/xdg_shared_mime_info/generate.py b/third_party/xdg_shared_mime_info/generate.py
index c2a3f89..835773ec 100755
--- a/third_party/xdg_shared_mime_info/generate.py
+++ b/third_party/xdg_shared_mime_info/generate.py
@@ -31,8 +31,8 @@
 
 # Fetch and save local copy of xml.
 filename = 'freedesktop.org.xml.in'
-url = ('https://raw.githubusercontent.com/freedesktop/xdg-shared-mime-info/'
-       'HEAD/data/%s' % filename)
+url = ('https://gitlab.freedesktop.org/xdg/shared-mime-info/-/raw/master/data/%s'
+       % filename)
 xml = urllib.request.urlopen(url).read()
 with open(os.path.join(current_dir, filename), 'wb') as f:
   f.write(xml)
diff --git a/third_party/xdg_shared_mime_info/mime_cache.cc.tmpl b/third_party/xdg_shared_mime_info/mime_cache.cc.tmpl
index d0ed673..68d5088 100644
--- a/third_party/xdg_shared_mime_info/mime_cache.cc.tmpl
+++ b/third_party/xdg_shared_mime_info/mime_cache.cc.tmpl
@@ -11,7 +11,6 @@
 // File generated by //third_party/xdg_mime_shared_info/generate.py.
 namespace xdg_shared_mime_info {
 
-
 bool GetMimeCacheTypeFromExtension(const std::string& ext,
                                    std::string* result) {
   static constexpr auto kMap = base::MakeFixedFlatMap<base::StringPiece,
diff --git a/third_party/xdg_shared_mime_info/mime_cache.gen.cc b/third_party/xdg_shared_mime_info/mime_cache.gen.cc
index f86d6a8..6c7d589 100644
--- a/third_party/xdg_shared_mime_info/mime_cache.gen.cc
+++ b/third_party/xdg_shared_mime_info/mime_cache.gen.cc
@@ -362,10 +362,11 @@
       {"j2c", "image/x-jp2-codestream"},
       {"j2k", "image/x-jp2-codestream"},
       {"jad", "text/vnd.sun.j2me.app-descriptor"},
-      {"jar", "application/x-java-archive"},
+      {"jar", "application/java-archive"},
       {"java", "text/x-java"},
       {"jceks", "application/x-java-jce-keystore"},
       {"jks", "application/x-java-keystore"},
+      {"jl", "text/julia"},
       {"jng", "image/x-jng"},
       {"jnlp", "application/x-java-jnlp-file"},
       {"jp2", "image/jp2"},
@@ -568,6 +569,7 @@
       {"old", "application/x-trash"},
       {"oleo", "application/x-oleo"},
       {"ooc", "text/x-ooc"},
+      {"openvpn", "application/x-openvpn-profile"},
       {"opml", "text/x-opml+xml"},
       {"oprc", "application/vnd.palm"},
       {"opus", "audio/ogg"},
@@ -582,6 +584,7 @@
       {"ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
       {"ott", "application/vnd.oasis.opendocument.text-template"},
       {"ova", "application/ovf"},
+      {"ovpn", "application/x-openvpn-profile"},
       {"owl", "application/rdf+xml"},
       {"owx", "application/owl+xml"},
       {"oxps", "application/oxps"},
@@ -704,6 +707,7 @@
       {"qml", "text/x-qml"},
       {"qmlproject", "text/x-qml"},
       {"qmltypes", "text/x-qml"},
+      {"qoi", "image/qoi"},
       {"qp", "application/x-qpress"},
       {"qs", "application/sparql-query"},
       {"qt", "video/quicktime"},
diff --git a/tools/android/asan/third_party/BUILD.gn b/tools/android/asan/third_party/BUILD.gn
index 02a24cd..f3d96603 100644
--- a/tools/android/asan/third_party/BUILD.gn
+++ b/tools/android/asan/third_party/BUILD.gn
@@ -24,7 +24,7 @@
     assert(false, "No ASAN library available for $target_cpu")
   }
 
-  _adb_path = "${android_sdk_root}/platform-tools/adb"
+  _adb_path = "${public_android_sdk_root}/platform-tools/adb"
   _lib_dir = "${clang_base_path}/lib/clang/${clang_version}/lib/linux"
   _lib_paths = []
   foreach(_lib_arch, _lib_archs) {
diff --git a/tools/android/avd/proto/creation/generic_androidu.textpb b/tools/android/avd/proto/creation/generic_androidu.textpb
index dc645fe..18dcdab 100644
--- a/tools/android/avd/proto/creation/generic_androidu.textpb
+++ b/tools/android/avd/proto/creation/generic_androidu.textpb
@@ -12,7 +12,7 @@
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-upsidedowncake/google_apis/x86_64"
-  version: "KG8hpMcAc1OrhFZ-UBuYb3GwK7a-3TZRq8UJqBsg9owC"  # r3
+  version: "KG8hpMcAc1OrhFZ-UBuYb3GwK7a-3TZRq8UJqBsg9owC"  # r3, UPB1.230309.013
   dest_path: "generic_androidu"
 }
 system_image_name: "system-images;android-UpsideDownCake;google_apis;x86_64"
diff --git a/tools/android/avd/proto/generic_androidu.textpb b/tools/android/avd/proto/generic_androidu.textpb
new file mode 100644
index 0000000..59292b4
--- /dev/null
+++ b/tools/android/avd/proto/generic_androidu.textpb
@@ -0,0 +1,25 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Configuration for a generic x86_64 android-U AVD (userdebug build).
+
+emulator_package {
+  package_name: "chromium/third_party/android_sdk/public/emulator"
+  version: "MSb1Vp2C8-H7iJoLte1wgL6oqDtFMLlpI8bppWCUL6EC"  # 32.1.12
+  dest_path: "generic_androidu"
+}
+
+system_image_package {
+  package_name: "chromium/third_party/android_sdk/public/system-images/android-upsidedowncake/google_apis/x86_64"
+  version: "KG8hpMcAc1OrhFZ-UBuYb3GwK7a-3TZRq8UJqBsg9owC"  # r3, UPB1.230309.013
+  dest_path: "generic_androidu"
+}
+system_image_name: "system-images;android-UpsideDownCake;google_apis;x86_64"
+
+avd_package {
+  package_name: "chromium/third_party/android_sdk/public/avds/android-upsidedowncake/google_apis/x86_64"
+  version: "WIgPA8j-YTI1W7MVC3eckiSYVxEDqLc7UZI3mkTjyPoC"  # created in bb_id 8781558768666395489
+  dest_path: "generic_androidu"
+}
+avd_name: "android_u_google_apis_x86_64"
diff --git a/tools/binary_size/libsupersize/viewer/static/main.css b/tools/binary_size/libsupersize/viewer/static/main.css
index 4606bbcc..7bbeb30 100644
--- a/tools/binary_size/libsupersize/viewer/static/main.css
+++ b/tools/binary_size/libsupersize/viewer/static/main.css
@@ -336,6 +336,10 @@
   white-space: pre;
 }
 
+#div-metadata-view table td.metadata-changed {
+  background: #ffc;
+}
+
 #div-review-info{
   font-size: 13px;
 }
diff --git a/tools/binary_size/libsupersize/viewer/static/metadata-tree-ui.js b/tools/binary_size/libsupersize/viewer/static/metadata-tree-ui.js
index 0c35d0e..9f5294d 100644
--- a/tools/binary_size/libsupersize/viewer/static/metadata-tree-ui.js
+++ b/tools/binary_size/libsupersize/viewer/static/metadata-tree-ui.js
@@ -57,7 +57,10 @@
   renderSimpleValue(value) {
     if (value === null)
       return 'null';
-    if (typeof value !== 'object')
+    const t = typeof value;
+    if (t === 'number')
+      return formatNumber(value);
+    if (t !== 'object')
       return value.toString();
     if (Array.isArray(value)) {
       if (value.every((v) => v === null || typeof v !== 'object'))
@@ -220,9 +223,14 @@
     for (const item of items) {
       const tr = document.createElement('tr');
       tr.appendChild(dom.textElement('td', item.name, ''));
-      if (diffMode)
-        tr.appendChild(dom.textElement('td', item.beforeValue.toString(), ''));
-      tr.appendChild(dom.textElement('td', item.value.toString(), ''));
+      const s2 = item.value.toString();
+      const td = dom.textElement('td', s2, '');
+      if (diffMode) {
+        const s1 = item.beforeValue.toString();
+        tr.appendChild(dom.textElement('td', s1, ''));
+        td.classList.toggle('metadata-changed', s1 !== s2);
+      }
+      tr.appendChild(td);
       table.appendChild(tr);
     }
   }
diff --git a/tools/binary_size/libsupersize/viewer/static/start-worker.js b/tools/binary_size/libsupersize/viewer/static/start-worker.js
index 7c28384..358eae28 100644
--- a/tools/binary_size/libsupersize/viewer/static/start-worker.js
+++ b/tools/binary_size/libsupersize/viewer/static/start-worker.js
@@ -4,12 +4,6 @@
 
 'use strict';
 
-/** @type {Object} */
-window.supersize = window.supersize || {};
-
-/** @type {?Worker} */
-window.supersize.worker = null;
-
 /**
  * We use a worker to keep large tree creation logic off the UI thread.
  * This class is used to interact with the worker.
diff --git a/tools/binary_size/libsupersize/viewer/static/ui-base.js b/tools/binary_size/libsupersize/viewer/static/ui-base.js
index 217a50d9..3bec2c2 100644
--- a/tools/binary_size/libsupersize/viewer/static/ui-base.js
+++ b/tools/binary_size/libsupersize/viewer/static/ui-base.js
@@ -78,6 +78,23 @@
 const _LOCALE =
     /** @type {Array<string>} */ (navigator.languages) || navigator.language;
 
+/** @type {Object} */
+window.supersize = window.supersize || {};
+
+/** @type {?Worker} */
+window.supersize.worker = null;
+
+/** @type {?Object} **/
+window.supersize.metadata = null;
+
+/**
+ * Helper to render cached metadata.
+ * @type {!function(): void}
+ **/
+window.supersize.prettyMetadata = () => {
+  console.log(JSON.stringify(window.supersize.metadata, null, 2));
+};
+
 /**
  * Returns shortName for a tree node.
  * @param {TreeNode} treeNode
diff --git a/tools/binary_size/libsupersize/viewer/static/ui-main.js b/tools/binary_size/libsupersize/viewer/static/ui-main.js
index 4bfbdb1..ff4befb 100644
--- a/tools/binary_size/libsupersize/viewer/static/ui-main.js
+++ b/tools/binary_size/libsupersize/viewer/static/ui-main.js
@@ -50,6 +50,7 @@
         '%cPro Tip: %cawait supersize.worker.openNode("$FILE_PATH")',
         'font-weight:bold;color:red;', '')
 
+    window.supersize.metadata = metadata;
     for (const key of ['size_file', 'before_size_file']) {
       if (metadata.hasOwnProperty(key))
         preprocessSizeFileInPlace(metadata[key]);
diff --git a/tools/crates/gnrt/BUILD.gn b/tools/crates/gnrt/BUILD.gn
index 119b80ae..6746ec3 100644
--- a/tools/crates/gnrt/BUILD.gn
+++ b/tools/crates/gnrt/BUILD.gn
@@ -23,6 +23,8 @@
     "//third_party/rust/anyhow/v1:test_support",
     "//third_party/rust/cargo_metadata/v0_14:test_support",
     "//third_party/rust/clap/v4:test_support",
+    "//third_party/rust/env_logger/v0_10:test_support",
+    "//third_party/rust/log/v0_4:test_support",
     "//third_party/rust/semver/v1:test_support",
     "//third_party/rust/serde/v1:lib",
     "//third_party/rust/serde_json/v1:test_support",
diff --git a/tools/crates/gnrt/download.rs b/tools/crates/gnrt/download.rs
index 5c35839..4fd7d66 100644
--- a/tools/crates/gnrt/download.rs
+++ b/tools/crates/gnrt/download.rs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use crate::crates;
+use crate::crates::{self, ThirdPartySource};
 use crate::manifest::CargoManifest;
 use crate::paths;
 use crate::util::{check_exit_ok, check_output, check_spawn, check_wait_with_output};
@@ -21,12 +21,9 @@
     security: bool,
     paths: &paths::ChromiumPaths,
 ) -> Result<()> {
-    let vendored_crate = crates::ChromiumVendoredCrate {
-        name: name.to_string(),
-        epoch: crates::Epoch::from_version(&version),
-    };
-    let build_path = paths.third_party.join(vendored_crate.build_path());
-    let crate_path = paths.third_party.join(vendored_crate.crate_path());
+    let vendored_crate = crates::VendoredCrate { name: name.into(), version: version.clone() };
+    let build_path = paths.third_party.join(ThirdPartySource::build_path(&vendored_crate));
+    let crate_path = paths.third_party.join(ThirdPartySource::crate_path(&vendored_crate));
 
     let url = format!(
         "{dir}/{name}/{name}-{version}.{suffix}",
diff --git a/tools/crates/gnrt/gen.rs b/tools/crates/gnrt/gen.rs
index 52b66ee..4f9a1d6 100644
--- a/tools/crates/gnrt/gen.rs
+++ b/tools/crates/gnrt/gen.rs
@@ -4,18 +4,18 @@
 
 use gnrt_lib::*;
 
-use crates::{ChromiumVendoredCrate, StdVendoredCrate};
+use crates::{Epoch, ThirdPartySource, VendoredCrate};
 use manifest::*;
 
 use crate::util::{check_exit_ok, check_spawn, check_wait_with_output, create_dirs_if_needed};
 
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::fs;
 use std::io::{self, Write};
 use std::path::{Path, PathBuf};
 use std::process;
 
-use anyhow::{ensure, format_err, Context, Result};
+use anyhow::{bail, ensure, format_err, Context, Result};
 
 pub fn generate(args: &clap::ArgMatches, paths: &paths::ChromiumPaths) -> Result<()> {
     if args.get_flag("for-std") {
@@ -28,54 +28,64 @@
 }
 
 fn generate_for_third_party(args: &clap::ArgMatches, paths: &paths::ChromiumPaths) -> Result<()> {
+    // Traverse our third-party directory to collect the set of vendored crates.
+    // Used to generate Cargo.toml [patch] sections, and later to check against
+    // `cargo metadata`'s dependency resolution to ensure we have all the crates
+    // we need. We sort `crates` for a stable ordering of [patch] sections.
+    let source = crates::ThirdPartySource::new(paths.third_party.clone())?;
+
     let manifest_contents =
         String::from_utf8(fs::read(paths.third_party.join("third_party.toml")).unwrap()).unwrap();
     let mut third_party_manifest: ThirdPartyManifest =
         toml::de::from_str(&manifest_contents).context("Could not parse third_party.toml")?;
 
-    // Collect special fields from third_party.toml.
-    //
-    // TODO(crbug.com/1291994): handle visibility separately for each kind.
-    let mut deps_visibility = HashMap::<ChromiumVendoredCrate, crates::Visibility>::new();
-    let mut build_script_outputs = HashMap::<ChromiumVendoredCrate, Vec<String>>::new();
-    let mut gn_variables_libs = HashMap::<ChromiumVendoredCrate, String>::new();
+    let mut crates_metadata = HashMap::<VendoredCrate, gn::PerCrateMetadata>::new();
 
-    let mut walk_deps = |dep_name: &str, dep_spec: &Dependency, visibility: crates::Visibility| {
-        let (version_req, is_public, dep_outputs, gn_variables_lib): (
-            &_,
-            bool,
-            &[_],
-            Option<&String>,
-        ) = match dep_spec {
-            Dependency::Short(version_req) => (version_req, true, &[], None),
-            Dependency::Full(dep) => (
-                dep.version.as_ref().unwrap(),
-                dep.allow_first_party_usage,
-                &dep.build_script_outputs,
-                dep.gn_variables_lib.as_ref(),
-            ),
-        };
-        let epoch = crates::Epoch::from_version_req_str(&version_req.0);
-        let crate_id = ChromiumVendoredCrate { name: dep_name.to_string(), epoch };
-        deps_visibility.insert(
-            crate_id.clone(),
-            if is_public { visibility } else { crates::Visibility::ThirdParty },
+    ensure!(
+        third_party_manifest.dependency_spec.build_dependencies.is_empty(),
+        "[build-dependencies] are not supported in third_party.toml"
+    );
+
+    // Collect the Chromium-specific fields from third_party.toml and store them
+    // in `crates_metadata`.
+    for (dep_name, dep_spec, dep_vis) in [
+        (
+            &third_party_manifest.dependency_spec.dev_dependencies,
+            crates::Visibility::TestOnlyAndThirdParty,
+        ),
+        (&third_party_manifest.dependency_spec.dependencies, crates::Visibility::Public),
+    ]
+    .into_iter()
+    .flat_map(|(list, vis)| list.iter().map(move |(name, spec)| (name, spec, vis)))
+    {
+        let full_dep: FullDependency = dep_spec.clone().into_full();
+        let version_req = semver::VersionReq::parse(
+            &full_dep
+                .version
+                .as_ref()
+                .ok_or_else(|| format_err!("{dep_name} is missing a version in third_party.toml"))?
+                .0,
+        )?;
+        let crate_id = source.find_match(dep_name, &version_req).ok_or_else(|| {
+            format_err!(
+                "{dep_name} {version_req} was requested in third_party.toml \
+                but not present in vendored crates"
+            )
+        })?;
+
+        crates_metadata.insert(
+            crate_id,
+            gn::PerCrateMetadata {
+                build_script_outputs: full_dep.build_script_outputs,
+                gn_variables: full_dep.gn_variables_lib,
+                visibility: if full_dep.allow_first_party_usage {
+                    dep_vis
+                } else {
+                    crates::Visibility::ThirdParty
+                },
+            },
         );
-        if !dep_outputs.is_empty() {
-            build_script_outputs.insert(crate_id.clone(), dep_outputs.to_vec());
-        }
-        if gn_variables_lib.is_some() {
-            gn_variables_libs.insert(crate_id, gn_variables_lib.unwrap().clone());
-        }
-    };
-
-    for (dep_name, dep_spec) in &third_party_manifest.dependency_spec.dev_dependencies {
-        walk_deps(dep_name, dep_spec, crates::Visibility::TestOnlyAndThirdParty)
     }
-    for (dep_name, dep_spec) in &third_party_manifest.dependency_spec.dependencies {
-        walk_deps(dep_name, dep_spec, crates::Visibility::Public)
-    }
-    // [build-dependencies] is not used in third_party.toml.
 
     // For crates used in first-party tests, we do not build a separate library from
     // production (unlike standard Rust tests, and those found in third-party
@@ -87,25 +97,10 @@
         .extend(std::mem::take(&mut third_party_manifest.dependency_spec.dev_dependencies));
 
     // Rebind as immutable.
-    let (third_party_manifest, deps_visibility, build_script_outputs, gn_variables_libs) =
-        (third_party_manifest, deps_visibility, build_script_outputs, gn_variables_libs);
-
-    // Traverse our third-party directory to collect the set of vendored crates.
-    // Used to generate Cargo.toml [patch] sections, and later to check against
-    // `cargo metadata`'s dependency resolution to ensure we have all the crates
-    // we need. We sort `crates` for a stable ordering of [patch] sections.
-    let mut crates = crates::collect_third_party_crates(paths.third_party.clone()).unwrap();
-    crates.sort_unstable_by(|a, b| a.0.cmp(&b.0));
+    let (third_party_manifest, crates_metadata) = (third_party_manifest, crates_metadata);
 
     // Generate a fake root Cargo.toml for dependency resolution.
-    let cargo_manifest = generate_fake_cargo_toml(
-        third_party_manifest,
-        crates.iter().map(|(c, _)| manifest::PatchSpecification {
-            package_name: c.name.clone(),
-            patch_name: c.patch_name(),
-            path: c.crate_path(),
-        }),
-    );
+    let cargo_manifest = generate_fake_cargo_toml(third_party_manifest, source.cargo_patches());
 
     if args.get_flag("output-cargo-toml") {
         println!("{}", toml::ser::to_string(&cargo_manifest).unwrap());
@@ -142,21 +137,27 @@
     // * Each discovered crate matches with a resolved dependency (no unused
     //   crates).
     let mut has_error = false;
-    let present_crates: HashSet<&ChromiumVendoredCrate> = crates.iter().map(|(c, _)| c).collect();
+    let present_crates = source.present_crates();
 
     // Construct the set of requested third-party crates, ensuring we don't have
     // duplicate epochs. For example, if we resolved two versions of a
     // dependency with the same major version, we cannot continue.
-    let mut req_crates = HashSet::<ChromiumVendoredCrate>::new();
-    for package in &dependencies {
-        if !req_crates.insert(package.third_party_crate_id()) {
-            panic!("found another requested package with the same name and epoch: {:?}", package);
+    let mut req_crates = HashMap::<(String, Epoch), Version>::new();
+    for dep in &dependencies {
+        let crate_id = dep.crate_id();
+        let epoch = crates::Epoch::from_version(&crate_id.version);
+        if let Some(conflict) =
+            req_crates.insert((crate_id.name.clone(), epoch), crate_id.version.clone())
+        {
+            bail!(
+                "Cargo resolved two different package versions of the same epoch: {} {} vs {}",
+                dep.package_name,
+                dep.version,
+                conflict
+            );
         }
-    }
-    let req_crates = req_crates;
 
-    for dep in dependencies.iter() {
-        if !present_crates.contains(&dep.third_party_crate_id()) {
+        if !present_crates.contains_key(&crate_id) {
             has_error = true;
             println!("Missing dependency: {} {}", dep.package_name, dep.version);
             for edge in dep.dependency_path.iter() {
@@ -179,9 +180,13 @@
             println!("    Resolved version: {}", dep.version);
         }
     }
+    let req_crates = req_crates;
 
-    for present_crate in present_crates.iter() {
-        if !req_crates.contains(present_crate) {
+    for present_crate in present_crates.keys() {
+        if !req_crates.contains_key(&(
+            present_crate.name.clone(),
+            Epoch::from_version(&present_crate.version),
+        )) {
             has_error = true;
             println!("Unused crate: {present_crate}");
         }
@@ -189,26 +194,22 @@
 
     ensure!(!has_error, "Dependency resolution failed");
 
-    let build_files: HashMap<ChromiumVendoredCrate, gn::BuildFile> =
-        gn::build_files_from_chromium_deps(
-            &dependencies,
-            &paths,
-            &crates.iter().cloned().collect(),
-            &build_script_outputs,
-            &deps_visibility,
-            &gn_variables_libs,
-        );
+    let build_files: HashMap<VendoredCrate, gn::BuildFile> =
+        gn::build_files_from_chromium_deps(&dependencies, &paths, &crates_metadata, |crate_id| {
+            // A missing crate should have been detected above, so unwrap.
+            present_crates.get(&crate_id).unwrap()
+        });
 
     // Before modifying anything make sure we have a one-to-one mapping of
     // discovered crates and build file data.
-    for (crate_id, _) in build_files.iter() {
+    for crate_id in build_files.keys() {
         // This shouldn't happen, but check anyway in case we have a strange
         // logic error above.
-        assert!(present_crates.contains(&crate_id));
+        assert!(present_crates.contains_key(crate_id));
     }
 
-    for crate_id in present_crates.iter() {
-        if !build_files.contains_key(*crate_id) {
+    for crate_id in present_crates.keys() {
+        if !build_files.contains_key(crate_id) {
             println!("Error: discovered crate {crate_id}, but no build file was generated.");
             has_error = true;
         }
@@ -218,7 +219,8 @@
 
     // Wipe all previous BUILD.gn files. If we fail, we don't want to leave a
     // mix of old and new build files.
-    for build_file in crates.iter().map(|(crate_id, _)| build_file_path(crate_id, &paths)) {
+    for build_file in present_crates.iter().map(|(crate_id, _)| build_file_path(&crate_id, &paths))
+    {
         if build_file.exists() {
             fs::remove_file(&build_file).unwrap();
         }
@@ -226,9 +228,9 @@
 
     // Generate build files, wiping the previous ones so we don't have any stale
     // build rules.
-    for (crate_id, _) in crates.iter() {
-        let build_file_path = build_file_path(crate_id, &paths);
-        let build_file_data = match build_files.get(&crate_id) {
+    for (crate_id, _) in present_crates.iter() {
+        let build_file_path = build_file_path(&crate_id, &paths);
+        let build_file_data = match build_files.get(crate_id) {
             Some(build_file) => build_file,
             None => panic!("missing build data for {crate_id}"),
         };
@@ -298,7 +300,7 @@
 
     // Check that all resolved third party deps are available. First, collect
     // the set of third-party dependencies vendored in the Rust source package.
-    let vendored_crates: HashMap<StdVendoredCrate, manifest::CargoPackage> =
+    let vendored_crates: HashMap<VendoredCrate, manifest::CargoPackage> =
         crates::collect_std_vendored_crates(paths.rust_src_vendor).unwrap().into_iter().collect();
 
     // Collect vendored dependencies, and also check that all resolved
@@ -324,11 +326,9 @@
         );
 
         vendored_crates
-            .get_key_value(&StdVendoredCrate {
+            .get_key_value(&VendoredCrate {
                 name: dep.package_name.clone(),
                 version: dep.version.clone(),
-                // Placeholder value for lookup.
-                is_latest: false,
             })
             .ok_or_else(|| {
                 format_err!(
@@ -345,10 +345,10 @@
     Ok(())
 }
 
-fn build_file_path(crate_id: &ChromiumVendoredCrate, paths: &paths::ChromiumPaths) -> PathBuf {
+fn build_file_path(crate_id: &VendoredCrate, paths: &paths::ChromiumPaths) -> PathBuf {
     let mut path = paths.root.clone();
     path.push(&paths.third_party);
-    path.push(crate_id.build_path());
+    path.push(ThirdPartySource::build_path(&crate_id));
     path.push("BUILD.gn");
     path
 }
diff --git a/tools/crates/gnrt/lib/BUILD.gn b/tools/crates/gnrt/lib/BUILD.gn
index e2675856..8c4fce5 100644
--- a/tools/crates/gnrt/lib/BUILD.gn
+++ b/tools/crates/gnrt/lib/BUILD.gn
@@ -23,11 +23,13 @@
     "manifest.rs",
     "paths.rs",
     "platforms.rs",
+    "util.rs",
   ]
 
   deps = [
     "//third_party/rust/cargo_metadata/v0_14:test_support",
     "//third_party/rust/cargo_platform/v0_1:test_support",
+    "//third_party/rust/log/v0_4:test_support",
     "//third_party/rust/once_cell/v1:test_support",
     "//third_party/rust/semver/v1:test_support",
     "//third_party/rust/serde/v1:lib",
diff --git a/tools/crates/gnrt/lib/crates.rs b/tools/crates/gnrt/lib/crates.rs
index 4f3c981..de1fe9c 100644
--- a/tools/crates/gnrt/lib/crates.rs
+++ b/tools/crates/gnrt/lib/crates.rs
@@ -4,8 +4,11 @@
 
 //! Utilities to handle vendored third-party crates.
 
+use crate::log_err;
 use crate::manifest;
+use crate::util::AsDebug;
 
+use std::collections::HashMap;
 use std::fmt::{self, Display};
 use std::fs;
 use std::hash::Hash;
@@ -13,9 +16,10 @@
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
+use log::{error, warn};
 use semver::Version;
 
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum Visibility {
     /// The crate can be used by any build targets.
     Public,
@@ -26,6 +30,14 @@
     TestOnlyAndThirdParty,
 }
 
+/// Returns a default of `ThirdParty`, which is the most conservative option and
+/// generally what we want if one isn't explicitly computed.
+impl std::default::Default for Visibility {
+    fn default() -> Self {
+        Visibility::ThirdParty
+    }
+}
+
 /// A normalized version as used in third_party/rust crate paths.
 ///
 /// A crate version is identified by the major version, if it's >= 1, or the
@@ -219,192 +231,152 @@
     }
 }
 
-/// Identifies a third-party crate vendored in the format described by
-/// third_party/rust/README.md
+/// Identifies a crate available in some vendored source. Each crate is uniquely
+/// identified by its Cargo.toml package name and version.
 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-pub struct ChromiumVendoredCrate {
-    /// The upstream name as specified in its Cargo.toml.
+pub struct VendoredCrate {
     pub name: String,
-    /// The crate's epoch, as defined by `Epoch`.
-    pub epoch: Epoch,
-}
-
-impl ChromiumVendoredCrate {
-    /// The normalized name we use in our vendoring structure.
-    pub fn normalized_name(&self) -> NormalizedName {
-        NormalizedName::from_crate_name(&self.name)
-    }
-
-    /// The location of this crate's directory, including its source subdir and
-    /// build files, relative to the third-party Rust crate directory. Crates
-    /// are laid out according to their name and epoch.
-    pub fn build_path(&self) -> PathBuf {
-        let mut path = PathBuf::new();
-        path.push(&self.normalized_name().0);
-        path.push(self.epoch.to_string());
-        path
-    }
-
-    /// The location of this crate's source relative to the third-party Rust
-    /// crate directory.
-    pub fn crate_path(&self) -> PathBuf {
-        let mut path = self.build_path();
-        path.push("crate");
-        path
-    }
-
-    /// Unique but arbitrary name for this (crate, epoch) pair. Suitable for use
-    /// in Cargo.toml [patch] sections.
-    pub fn patch_name(&self) -> String {
-        format!("{}_{}", self.name, self.epoch)
-    }
-}
-
-impl fmt::Display for ChromiumVendoredCrate {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_fmt(format_args!("{} {}", self.name, self.epoch))
-    }
-}
-
-/// Traverse vendored third-party crates and collect the set of names and
-/// epochs. Each `ChromiumVendoredCrate` entry is paired with the package
-/// metadata from its manifest. The returned list is in unspecified order.
-pub fn collect_third_party_crates<P: AsRef<Path>>(
-    crates_path: P,
-) -> io::Result<Vec<(ChromiumVendoredCrate, manifest::CargoPackage)>> {
-    let mut crates = Vec::new();
-
-    for crate_dir in fs::read_dir(crates_path)? {
-        // Look at each crate directory.
-        let crate_dir: fs::DirEntry = crate_dir?;
-        if !crate_dir.file_type()?.is_dir() {
-            continue;
-        }
-
-        let crate_path = crate_dir.path();
-
-        // Ensure the path has a valid name: is UTF8, has our normalized format.
-        let normalized_name = path_as_str(crate_path.file_name().unwrap())?;
-        into_io_result(NormalizedName::new(normalized_name).ok_or_else(|| {
-            format!("unnormalized crate name in path {}", crate_path.to_string_lossy())
-        }))?;
-
-        for epoch_dir in fs::read_dir(crate_dir.path())? {
-            // Look at each epoch of the crate we have checked in.
-            let epoch_dir: fs::DirEntry = epoch_dir?;
-            if !crate_dir.file_type()?.is_dir() {
-                continue;
-            }
-
-            let epoch_path = epoch_dir.path();
-            let epoch_name = path_as_str(epoch_path.file_name().unwrap())?;
-            let epoch = match Epoch::from_str(epoch_name) {
-                Ok(epoch) => epoch,
-                // Skip if we can't parse the directory as a valid epoch.
-                Err(_) => continue,
-            };
-
-            // Try to read the Cargo.toml, since we need the package name. The
-            // directory name on disk is normalized but we need to refer to the
-            // package the way Cargo expects.
-            let manifest_path = epoch_path.join("crate/Cargo.toml");
-            let manifest_contents = match fs::read_to_string(&manifest_path) {
-                Ok(s) => s,
-                Err(e) => match e.kind() {
-                    // Skip this directory and log a message if a Cargo.toml
-                    // doesn't exist.
-                    io::ErrorKind::NotFound => {
-                        println!(
-                            "Warning: directory name parsed as valid epoch but \
-                            contained no Cargo.toml: {}",
-                            manifest_path.to_string_lossy()
-                        );
-                        continue;
-                    }
-                    _ => return Err(e),
-                },
-            };
-            let manifest: manifest::CargoManifest = toml::de::from_str(&manifest_contents).unwrap();
-            let package_name = manifest.package.name.clone();
-
-            crates.push((ChromiumVendoredCrate { name: package_name, epoch }, manifest.package));
-        }
-    }
-
-    Ok(crates)
-}
-
-/// Identifies a third-party crate vendored in `cargo vendor` format. This is
-/// used in the Rust source tree.
-#[derive(Clone, Debug, Eq)]
-pub struct StdVendoredCrate {
-    /// The upstream name as specified in its Cargo.toml.
-    pub name: String,
-    /// The crate's version.
     pub version: Version,
-    /// Whether this is the latest version in the vendored set. If this is
-    /// false, there is a version suffix in the path. Otherwise, it is just the
-    /// package name.
-    ///
-    /// This is not factored into comparisons or hashing.
-    pub is_latest: bool,
 }
 
-impl Hash for StdVendoredCrate {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.name.hash(state);
-        self.version.hash(state);
-    }
-}
-
-impl PartialEq for StdVendoredCrate {
-    fn eq(&self, other: &Self) -> bool {
-        self.name == other.name && self.version == other.version
-    }
-}
-
-impl Ord for StdVendoredCrate {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.name.cmp(&other.name).then(self.version.cmp(&other.version))
-    }
-}
-
-impl PartialOrd for StdVendoredCrate {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl StdVendoredCrate {
-    /// The crate's path relative to the `vendor/` dir, taking into account
-    /// whether it's the latest version (and so whether the name is abridged).
-    pub fn crate_path(&self) -> PathBuf {
-        PathBuf::from(if self.is_latest { self.abridged_dir_name() } else { self.full_dir_name() })
-    }
-
-    /// The subdirectory name including the full version.
-    fn full_dir_name(&self) -> String {
-        format!("{}-{}", self.name, self.version)
-    }
-
-    /// The subdirectory name without the version.
-    fn abridged_dir_name(&self) -> String {
-        self.name.clone()
-    }
-}
-
-impl fmt::Display for StdVendoredCrate {
+impl fmt::Display for VendoredCrate {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{} {}", self.name, self.version)
     }
 }
 
+impl VendoredCrate {
+    pub fn normalized_name(&self) -> NormalizedName {
+        NormalizedName::from_crate_name(&self.name)
+    }
+}
+
+/// Set of vendored packages in `//third_party/rust` format. Namely, foo 1.2.3
+/// would be in `<root>/foo/v1/crate` and bar 0.1.2 would be in
+/// `<root>/bar/v0_1/crate`. The names also must be normalized according to
+/// `NormalizedName` rules. Multiple versions of a name can exist, as long as
+/// their "epoch" (vX for 1.0.0+ or vO_Y for <1.0.0) does not collide. This is
+/// enforced naturally by the directory layout.
+pub struct ThirdPartySource {
+    /// The available set of versions for each crate.
+    crate_versions: HashMap<String, Vec<Version>>,
+    /// As an optimization, cache the parsed manifest for each crate: it's
+    /// needed later, and we have to parse it here anyway.
+    manifests: HashMap<VendoredCrate, manifest::CargoPackage>,
+}
+
+impl ThirdPartySource {
+    /// Collects set of vendored crates on disk.
+    pub fn new(crates_path: &Path) -> io::Result<Self> {
+        let mut crate_versions = HashMap::<String, Vec<Version>>::new();
+        let mut manifests = HashMap::new();
+
+        for crate_dir in log_err!(
+            fs::read_dir(crates_path),
+            "reading dir {crates_path}",
+            crates_path = AsDebug(crates_path)
+        )? {
+            // Look at each crate directory.
+            let crate_dir: fs::DirEntry = log_err!(crate_dir)?;
+            if !crate_dir.file_type()?.is_dir() {
+                continue;
+            }
+
+            let crate_path = crate_dir.path();
+
+            // Ensure the path has a valid name: is UTF8, has our normalized format.
+            let normalized_name = path_as_str(crate_path.file_name().unwrap())?;
+            into_io_result(NormalizedName::new(normalized_name).ok_or_else(|| {
+                format!("unnormalized crate name in path {}", crate_path.to_string_lossy())
+            }))?;
+
+            for epoch_dir in fs::read_dir(crate_dir.path())? {
+                // Look at each epoch of the crate we have checked in.
+                let epoch_dir: fs::DirEntry = epoch_dir?;
+                if !epoch_dir.file_type()?.is_dir() {
+                    continue;
+                }
+
+                // Skip it if it's not a valid epoch.
+                if epoch_dir.file_name().to_str().and_then(|s| Epoch::from_str(s).ok()).is_none() {
+                    continue;
+                }
+
+                let crate_path = epoch_dir.path().join("crate");
+
+                let Some((crate_id, manifest)) = get_vendored_crate_info(&crate_path)? else {
+                    warn!("directory name parsed as valid epoch but contained no Cargo.toml: {}",
+                          crate_path.to_string_lossy());
+                    continue;
+                };
+
+                manifests.insert(crate_id.clone(), manifest.package);
+                crate_versions.entry(crate_id.name).or_default().push(crate_id.version);
+            }
+        }
+
+        Ok(ThirdPartySource { crate_versions, manifests })
+    }
+
+    /// Find crate with `name` that meets version requirement. Returns `None` if
+    /// there are none.
+    pub fn find_match(&self, name: &str, req: &semver::VersionReq) -> Option<VendoredCrate> {
+        let (key, versions) = self.crate_versions.get_key_value(name)?;
+        let version = versions.iter().find(|v| req.matches(v))?.clone();
+        Some(VendoredCrate { name: key.clone(), version })
+    }
+
+    pub fn present_crates(&self) -> &HashMap<VendoredCrate, manifest::CargoPackage> {
+        &self.manifests
+    }
+
+    /// Get Cargo.toml `[patch]` sections for each third-party crate.
+    pub fn cargo_patches(&self) -> Vec<manifest::PatchSpecification> {
+        let mut patches: Vec<_> = self
+            .manifests
+            .iter()
+            .map(|(c, _)| manifest::PatchSpecification {
+                package_name: c.name.clone(),
+                patch_name: format!(
+                    "{name}_{epoch}",
+                    name = c.name,
+                    epoch = Epoch::from_version(&c.version)
+                ),
+                path: Self::crate_path(c),
+            })
+            .collect();
+        // Give patches a stable ordering, instead of the arbitrary HashMap
+        // order.
+        patches.sort_unstable_by(|p1, p2| p1.patch_name.cmp(&p2.patch_name));
+        patches
+    }
+
+    /// Get the root of `id`'s sources relative to the vendor dir.
+    pub fn crate_path(id: &VendoredCrate) -> PathBuf {
+        let mut path: PathBuf = Self::build_path(id);
+        path.push("crate");
+        path
+    }
+
+    /// Get the BUILD.gn file directory of `id` relative to the vendor dir.
+    pub fn build_path(id: &VendoredCrate) -> PathBuf {
+        let mut path: PathBuf = id.normalized_name().0.into();
+        path.push(Epoch::from_version(&id.version).to_string());
+        path
+    }
+}
+
+/// Get the subdir name containing `id` in a `cargo vendor` directory.
+pub fn std_crate_path(id: &VendoredCrate) -> PathBuf {
+    format!("{}-{}", id.name, id.version).into()
+}
+
 /// Traverse vendored third-party crates in the Rust source package. Each
-/// `StdVendoredCrate` is paired with the package metadata from its manifest.
-/// The returned list is in unspecified order.
-pub fn collect_std_vendored_crates<P: AsRef<Path>>(
-    vendor_path: P,
-) -> io::Result<Vec<(StdVendoredCrate, manifest::CargoPackage)>> {
+/// `VendoredCrate` is paired with the package metadata from its manifest. The
+/// returned list is in unspecified order.
+pub fn collect_std_vendored_crates(
+    vendor_path: &Path,
+) -> io::Result<Vec<(VendoredCrate, manifest::CargoPackage)>> {
     let mut crates = Vec::new();
 
     for vendored_crate in fs::read_dir(vendor_path)? {
@@ -413,42 +385,24 @@
             continue;
         }
 
-        let crate_path = vendored_crate.path();
-        let manifest_path = crate_path.join("Cargo.toml");
-        let manifest: manifest::CargoManifest =
-            toml::de::from_str(&fs::read_to_string(&manifest_path)?).unwrap();
-
-        // Vendored crate directories are named as either "{package_name}" or
-        // "{package_name}-{version}". The latest version of the vendored
-        // package is named as the former, and older ones as the latter.
-        //
-        // We must determine which format is used. A simple way is to compute
-        // both names and compare it to the directory.
-        let dir_name = crate_path.file_name().unwrap().to_str().unwrap();
-
-        let mut crate_id = StdVendoredCrate {
-            name: manifest.package.name.clone(),
-            version: manifest.package.version.clone(),
-            // Placeholder value.
-            is_latest: false,
+        let Some((crate_id, manifest)) = get_vendored_crate_info(&vendored_crate.path())? else {
+            error!("Cargo.toml not found at {}. cargo vendor would not do that to us.",
+                   vendored_crate.path().to_string_lossy());
+            panic!()
         };
 
-        crate_id.is_latest = if crate_id.full_dir_name() == dir_name {
-            false
-        } else if crate_id.abridged_dir_name() == dir_name {
-            true
-        } else {
+        // Vendored crate directories can be named "{package_name}" or
+        // "{package_name}-{version}", but for now we only use the latter for
+        // std vendored deps. For simplicity, accept only that.
+        let dir_name = vendored_crate.file_name().to_string_lossy().into_owned();
+        if std_crate_path(&crate_id) != Path::new(&dir_name) {
             return Err(io::Error::new(
                 io::ErrorKind::Other,
                 format!(
                     "directory name {dir_name} does not match package information for {crate_id:?}"
                 ),
             ));
-        };
-
-        // Check that we correctly determined `is_latest` and our computed
-        // subdirectory name is correct.
-        assert_eq!(crate_path.file_name().unwrap(), std::ffi::OsStr::new(&crate_id.crate_path()));
+        }
 
         crates.push((crate_id, manifest.package));
     }
@@ -456,6 +410,27 @@
     Ok(crates)
 }
 
+/// Get a crate's ID and parsed manifest from its path. Returns `Ok(None)` if
+/// there was no Cargo.toml, or `Err(_)` for other IO errors.
+fn get_vendored_crate_info(
+    package_path: &Path,
+) -> io::Result<Option<(VendoredCrate, manifest::CargoManifest)>> {
+    let manifest_file = match fs::read_to_string(package_path.join("Cargo.toml")) {
+        Ok(f) => f,
+        Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(None),
+        Err(e) => return Err(e),
+    };
+
+    let manifest: manifest::CargoManifest = toml::de::from_str(&manifest_file).unwrap();
+
+    let crate_id = VendoredCrate {
+        name: manifest.package.name.as_str().into(),
+        version: manifest.package.version.clone(),
+    };
+
+    Ok(Some((crate_id, manifest)))
+}
+
 /// Utility to read a path as a `&str` with an informative error message if it
 /// had invalid UTF8.
 fn path_as_str<T: AsRef<Path> + ?Sized>(path: &T) -> io::Result<&str> {
diff --git a/tools/crates/gnrt/lib/deps.rs b/tools/crates/gnrt/lib/deps.rs
index 54e09d9..f9f4875 100644
--- a/tools/crates/gnrt/lib/deps.rs
+++ b/tools/crates/gnrt/lib/deps.rs
@@ -4,7 +4,7 @@
 
 //! Utilities to process `cargo metadata` dependency graph.
 
-use crate::crates::{self, Epoch};
+use crate::crates;
 use crate::platforms::{self, Platform, PlatformSet};
 
 use std::collections::{hash_map::Entry, HashMap, HashSet};
@@ -64,11 +64,8 @@
 }
 
 impl Package {
-    pub fn third_party_crate_id(&self) -> crates::ChromiumVendoredCrate {
-        crates::ChromiumVendoredCrate {
-            name: self.package_name.clone(),
-            epoch: Epoch::from_version(&self.version),
-        }
+    pub fn crate_id(&self) -> crates::VendoredCrate {
+        crates::VendoredCrate { name: self.package_name.clone(), version: self.version.clone() }
     }
 }
 
@@ -89,11 +86,8 @@
 }
 
 impl DepOfDep {
-    pub fn third_party_crate_id(&self) -> crates::ChromiumVendoredCrate {
-        crates::ChromiumVendoredCrate {
-            name: self.package_name.clone(),
-            epoch: Epoch::from_version(&self.version),
-        }
+    pub fn crate_id(&self) -> crates::VendoredCrate {
+        crates::VendoredCrate { name: self.package_name.clone(), version: self.version.clone() }
     }
 }
 
diff --git a/tools/crates/gnrt/lib/gn.rs b/tools/crates/gnrt/lib/gn.rs
index b18c99e3..f1c561ec 100644
--- a/tools/crates/gnrt/lib/gn.rs
+++ b/tools/crates/gnrt/lib/gn.rs
@@ -119,6 +119,17 @@
     }
 }
 
+/// Extra metadata influencing GN output for a particular crate.
+#[derive(Clone, Debug, Default)]
+pub struct PerCrateMetadata {
+    /// Names of files the build.rs script may output.
+    pub build_script_outputs: Vec<String>,
+    /// Extra GN code pasted literally into the build rule.
+    pub gn_variables: Option<String>,
+    /// GN target visibility.
+    pub visibility: Visibility,
+}
+
 /// Generate `BuildFile` descriptions for each third party crate in the
 /// dependency graph.
 ///
@@ -128,24 +139,21 @@
 ///   script for each package.
 /// * `deps_visibility` is the visibility for each package, defining if it can
 ///   be used outside of third-party code and outside of tests.
-pub fn build_files_from_chromium_deps<'a, 'b, Iter: IntoIterator<Item = &'a deps::Package>>(
+pub fn build_files_from_chromium_deps<'a, 'b, Iter, GetManifest>(
     deps: Iter,
-    paths: &'b paths::ChromiumPaths,
-    metadata: &HashMap<ChromiumVendoredCrate, CargoPackage>,
-    build_script_outputs: &HashMap<ChromiumVendoredCrate, Vec<String>>,
-    deps_visibility: &HashMap<ChromiumVendoredCrate, Visibility>,
-    gn_variables_libs: &HashMap<ChromiumVendoredCrate, String>,
-) -> HashMap<ChromiumVendoredCrate, BuildFile> {
+    paths: &paths::ChromiumPaths,
+    metadatas: &HashMap<VendoredCrate, PerCrateMetadata>,
+    get_manifest: GetManifest,
+) -> HashMap<VendoredCrate, BuildFile>
+where
+    Iter: IntoIterator<Item = &'a deps::Package>,
+    GetManifest: Fn(&VendoredCrate) -> &'b CargoPackage,
+{
     deps.into_iter()
         .filter_map(|dep| {
-            make_build_file_for_chromium_dep(
-                dep,
-                paths,
-                metadata,
-                build_script_outputs,
-                deps_visibility,
-                gn_variables_libs,
-            )
+            let crate_id = dep.crate_id();
+            let metadata = metadatas.get(&crate_id).cloned().unwrap_or_default();
+            make_build_file_for_chromium_dep(dep, paths, get_manifest(&crate_id), metadata)
         })
         .collect()
 }
@@ -175,7 +183,7 @@
     // Used by reference if the provided crate config is empty.
     let default_crate_config = Default::default();
     let crate_config =
-        extra_config.per_crate_config.get(&dep.package_name).unwrap_or(&default_crate_config);
+        extra_config.per_crate_config.get(&*dep.package_name).unwrap_or(&default_crate_config);
     let all_config = &extra_config.all_config;
 
     // Helper macro to iterate over a particular config field: first the
@@ -221,7 +229,7 @@
         edition: dep.edition.clone(),
         cargo_pkg_version: dep.version.to_string(),
         cargo_pkg_authors,
-        cargo_pkg_name: dep.package_name.clone(),
+        cargo_pkg_name: dep.package_name.to_string(),
         cargo_pkg_description: dep.description.clone(),
         add_library_configs: Vec::new(),
         remove_library_configs: Vec::new(),
@@ -249,7 +257,11 @@
 
     // Add only normal dependencies: we don't run unit tests, and we don't run
     // build scripts (instead manually configuring build flags and env vars).
-    for dep_of_dep in dep.dependencies.iter().filter(|d| !exclude_deps.contains(&d.package_name)) {
+    for dep_of_dep in dep
+        .dependencies
+        .iter()
+        .filter(|d| exclude_deps.iter().find(|e| e.as_str() == &*d.package_name).is_none())
+    {
         let cond = match &dep_of_dep.platform {
             None => Condition::Always,
             Some(p) => Condition::If(platform_to_condition(p)),
@@ -290,40 +302,35 @@
 
 /// Generate the `BuildFile` for `dep`, or return `None` if no rules would be
 /// present.
-fn make_build_file_for_chromium_dep(
+fn make_build_file_for_chromium_dep<'a>(
     dep: &deps::Package,
     paths: &paths::ChromiumPaths,
-    metadata: &HashMap<ChromiumVendoredCrate, CargoPackage>,
-    build_script_outputs: &HashMap<ChromiumVendoredCrate, Vec<String>>,
-    deps_visibility: &HashMap<ChromiumVendoredCrate, Visibility>,
-    gn_variables_libs: &HashMap<ChromiumVendoredCrate, String>,
-) -> Option<(ChromiumVendoredCrate, BuildFile)> {
+    manifest: &CargoPackage,
+    metadata: PerCrateMetadata,
+) -> Option<(VendoredCrate, BuildFile)> {
     let third_party_path_str = paths.third_party.to_str().unwrap();
-    let crate_id = dep.third_party_crate_id();
-    let crate_abs_path = paths.root.join(paths.third_party.join(crate_id.build_path()));
+    let crate_id = dep.crate_id();
+    let crate_abs_path =
+        paths.root.join(paths.third_party.join(ThirdPartySource::build_path(&crate_id)));
 
     let to_gn_path = |abs_path: &Path| {
         abs_path.strip_prefix(&crate_abs_path).unwrap().to_string_lossy().into_owned()
     };
 
-    let package_metadata = metadata.get(&crate_id).unwrap();
-    let cargo_pkg_description = package_metadata.description.clone();
-    let cargo_pkg_authors = if package_metadata.authors.is_empty() {
-        None
-    } else {
-        Some(package_metadata.authors.join(", "))
-    };
+    let cargo_pkg_description = manifest.description.clone();
+    let cargo_pkg_authors =
+        if manifest.authors.is_empty() { None } else { Some(manifest.authors.join(", ")) };
 
     // Template for all the rules in a build file. Several fields are
     // the same for all a package's rules.
     let mut rule_template = RuleConcrete {
-        edition: package_metadata.edition.0.clone(),
-        cargo_pkg_version: package_metadata.version.to_string(),
+        edition: manifest.edition.to_string(),
+        cargo_pkg_version: manifest.version.to_string(),
         cargo_pkg_authors: cargo_pkg_authors,
-        cargo_pkg_name: package_metadata.name.clone(),
+        cargo_pkg_name: manifest.name.clone(),
         cargo_pkg_description,
         build_root: dep.build_script.as_ref().map(|p| to_gn_path(p.as_path())),
-        build_script_outputs: build_script_outputs.get(&crate_id).cloned().unwrap_or_default(),
+        build_script_outputs: metadata.build_script_outputs,
         ..Default::default()
     };
 
@@ -346,10 +353,10 @@
                 Some(p) => Condition::If(platform_to_condition(p)),
             };
 
-            let crate_id = dep_of_dep.third_party_crate_id();
+            let crate_id = dep_of_dep.crate_id();
             let normalized_name = crate_id.normalized_name();
             let dep_use_name = dep_of_dep.use_name.as_str();
-            let epoch = crate_id.epoch;
+            let epoch = Epoch::from_version(&crate_id.version);
             let rule = format!("//{third_party_path_str}/{normalized_name}/{epoch}:{target_name}");
 
             if dep_use_name != normalized_name.as_str() {
@@ -431,15 +438,14 @@
 
             let mut lib_details = rule_template.clone();
             lib_details.crate_name = Some(crate_id.normalized_name().to_string());
-            lib_details.epoch = Some(crate_id.epoch);
+            lib_details.epoch = Some(Epoch::from_version(&crate_id.version));
             lib_details.crate_type = lib_target.lib_type.to_string();
             lib_details.crate_root = to_gn_path(lib_target.root.as_path());
             lib_details.features = per_kind_info.features.clone();
-            lib_details.gn_variables_lib = gn_variables_libs.get(&crate_id).cloned();
+            lib_details.gn_variables_lib = metadata.gn_variables.clone();
 
             let testonly = dep_kind == deps::DependencyKind::Development;
-            let visibility =
-                deps_visibility.get(&crate_id).map(Clone::clone).unwrap_or(Visibility::ThirdParty);
+            let visibility = metadata.visibility;
 
             let lib_rule = Rule::Concrete {
                 common: RuleCommon {
diff --git a/tools/crates/gnrt/lib/lib.rs b/tools/crates/gnrt/lib/lib.rs
index 1c60a16..37024e7 100644
--- a/tools/crates/gnrt/lib/lib.rs
+++ b/tools/crates/gnrt/lib/lib.rs
@@ -9,3 +9,4 @@
 pub mod manifest;
 pub mod paths;
 pub mod platforms;
+pub mod util;
diff --git a/tools/crates/gnrt/lib/manifest.rs b/tools/crates/gnrt/lib/manifest.rs
index c4e3fe6..085c37dc 100644
--- a/tools/crates/gnrt/lib/manifest.rs
+++ b/tools/crates/gnrt/lib/manifest.rs
@@ -83,6 +83,15 @@
     Full(FullDependency),
 }
 
+impl Dependency {
+    pub fn into_full(self) -> FullDependency {
+        match self {
+            Self::Short(version) => FullDependency { version: Some(version), ..Default::default() },
+            Self::Full(full) => full,
+        }
+    }
+}
+
 /// A single crate dependency with some extra fields from third_party.toml.
 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
 #[serde(rename_all = "kebab-case")]
@@ -109,6 +118,19 @@
     pub gn_variables_lib: Option<String>,
 }
 
+impl Default for FullDependency {
+    fn default() -> Self {
+        FullDependency {
+            default_features: true,
+            version: None,
+            features: vec![],
+            allow_first_party_usage: true,
+            build_script_outputs: vec![],
+            gn_variables_lib: None,
+        }
+    }
+}
+
 /// Representation of a Cargo.toml file.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 #[serde(rename_all = "kebab-case")]
diff --git a/tools/crates/gnrt/lib/util.rs b/tools/crates/gnrt/lib/util.rs
new file mode 100644
index 0000000..86738f9
--- /dev/null
+++ b/tools/crates/gnrt/lib/util.rs
@@ -0,0 +1,39 @@
+// 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.
+
+//! General helpers
+
+/// Given a `Result<_, E>` expression where `E: Display`, log the `Err(e)` case
+/// and pass the result through.
+#[macro_export]
+macro_rules! log_err {
+    ($res_expr:expr, $ctx:expr $(, $name:ident = $arg:expr)*) => {
+        {
+            let res = $res_expr;
+            if let Err(e) = &res {
+                ::log::error!(concat!($ctx, ": {__error}"), __error = e $(, $name = $arg)*);
+            }
+            res
+        }
+    };
+
+    ($res_expr:expr) => {
+        {
+            let res = $res_expr;
+            if let Err(e) = &res {
+                ::log::error!("`{expr_str}`: {e}", expr_str = stringify!($res_expr));
+            }
+            res
+        }
+    }
+}
+
+/// Wrap a value `T: Debug` to give it a `Display` instance.
+pub struct AsDebug<T>(pub T);
+
+impl<T: std::fmt::Debug> std::fmt::Display for AsDebug<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        <T as std::fmt::Debug>::fmt(&self.0, f)
+    }
+}
diff --git a/tools/crates/gnrt/main.rs b/tools/crates/gnrt/main.rs
index bb54939..af76865 100644
--- a/tools/crates/gnrt/main.rs
+++ b/tools/crates/gnrt/main.rs
@@ -12,6 +12,13 @@
 use clap::arg;
 
 fn main() -> Result<()> {
+    let mut logger_builder = env_logger::Builder::new();
+    logger_builder.write_style(env_logger::WriteStyle::Always);
+    logger_builder.filter(None, log::LevelFilter::Warn);
+    logger_builder.parse_default_env();
+    logger_builder.format(format_log_entry);
+    logger_builder.init();
+
     let args = clap::Command::new("gnrt")
         .subcommand(
             clap::Command::new("gen")
@@ -54,3 +61,20 @@
         _ => unreachable!("Invalid subcommand"),
     }
 }
+
+fn format_log_entry(
+    fmt: &mut env_logger::fmt::Formatter,
+    record: &log::Record,
+) -> std::io::Result<()> {
+    use std::io::Write;
+
+    let level = fmt.default_styled_level(record.level());
+    write!(fmt, "[{level}")?;
+    if let Some(f) = record.file() {
+        write!(fmt, " {f}")?;
+        if let Some(l) = record.line() {
+            write!(fmt, ":{l}")?;
+        }
+    }
+    writeln!(fmt, "] {msg}", msg = record.args())
+}
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 9f798ce..a7d90ec 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -501,71 +501,42 @@
 
     'chromium.goma': {
       'Chromium Android ARM 32-bit Goma RBE Staging': 'android_release_bot_minimal_symbols',
-      'Chromium Android ARM 32-bit Goma RBE ToT': 'android_release_bot_minimal_symbols',
       # TODO(b/167942918): re-enable java goma?
-      'Chromium Android ARM 32-bit Goma RBE ToT (ATS)': 'android_release_bot_minimal_symbols',
 
       'Chromium Linux Goma RBE Staging': 'release_bot',
-      'Chromium Linux Goma RBE Staging (clobber)': 'release_bot',
       'Chromium Linux Goma RBE Staging (dbg)': 'debug_bot',
-      'Chromium Linux Goma RBE Staging (dbg) (clobber)': 'debug_bot',
-      'Chromium Linux Goma RBE ToT': 'release_bot',
-      'Chromium Linux Goma RBE ToT (ATS)': 'release_bot',
 
       'Chromium Mac Goma RBE Staging': 'release_bot',
-      'Chromium Mac Goma RBE Staging (clobber)': 'release_bot',
       'Chromium Mac Goma RBE Staging (dbg)': 'debug_bot',
-      'Chromium Mac Goma RBE ToT': 'release_bot',
 
       'Chromium Win Goma RBE ATS Staging': 'release_bot_x86_minimal_symbols',
-      'Chromium Win Goma RBE ATS Staging (clobber)': 'release_bot_x86_minimal_symbols',
-      'Chromium Win Goma RBE ATS ToT': 'release_bot_x86_minimal_symbols',
       'Chromium Win Goma RBE Staging': 'release_bot_x86_minimal_symbols',
-      'Chromium Win Goma RBE Staging (clobber)': 'release_bot_x86_minimal_symbols',
-      'Chromium Win Goma RBE ToT': 'release_bot_x86_minimal_symbols',
-
-      'Chromium iOS Goma RBE ToT': 'ios_device_release_compile_only',
 
       'chromeos-amd64-generic-rel-goma-rbe-staging': 'chromeos_amd64-generic_use_fake_dbus_clients_vm_optimized_goma',
-      'chromeos-amd64-generic-rel-goma-rbe-tot': 'chromeos_amd64-generic_use_fake_dbus_clients_vm_optimized_goma',
     },
 
     'chromium.goma.fyi': {
       'Linux Builder Goma RBE Canary': 'gpu_tests_release_bot',
-      'Linux Builder Goma RBE Latest Client': 'gpu_tests_release_bot',
 
       'Mac Builder (dbg) Goma RBE Canary (clobber)': 'gpu_tests_debug_bot',
-      'Mac Builder (dbg) Goma RBE Latest Client (clobber)': 'gpu_tests_debug_bot',
       'Mac M1 Builder (dbg) Goma RBE Canary (clobber)': 'gpu_tests_debug_bot_arm64',
 
       'Win Builder (dbg) Goma RBE ATS Canary': 'gpu_tests_debug_bot_x86_no_symbols',
-      'Win Builder (dbg) Goma RBE ATS Latest Client': 'gpu_tests_debug_bot_x86_no_symbols',
       'Win Builder (dbg) Goma RBE Canary': 'gpu_tests_debug_bot_x86_no_symbols',
-      'Win Builder (dbg) Goma RBE Latest Client': 'gpu_tests_debug_bot_x86_no_symbols',
       'Win Builder Goma RBE ATS Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
-      'Win Builder Goma RBE ATS Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
       'Win Builder Goma RBE Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
-      'Win Builder Goma RBE Canary (clobber)': 'gpu_tests_release_bot_x86_minimal_symbols',
-      'Win Builder Goma RBE Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
 
       'android-archive-dbg-goma-rbe-ats-canary': 'android_without_codecs_debug_bot',
-      'android-archive-dbg-goma-rbe-ats-latest': 'android_without_codecs_debug_bot',
       'android-archive-dbg-goma-rbe-canary': 'android_without_codecs_debug_bot',
-      'android-archive-dbg-goma-rbe-latest': 'android_without_codecs_debug_bot',
 
       'chromeos-amd64-generic-rel-goma-rbe-canary': 'chromeos_amd64-generic_use_fake_dbus_clients_vm_optimized_goma',
-      'chromeos-amd64-generic-rel-goma-rbe-latest': 'chromeos_amd64-generic_use_fake_dbus_clients_vm_optimized_goma',
 
       'ios-device-goma-rbe-canary-clobber': 'ios_device_release_compile_only',
-      'ios-device-goma-rbe-latest-clobber': 'ios_device_release_compile_only',
 
       'linux-archive-rel-goma-rbe-ats-canary': 'release_bot',
-      'linux-archive-rel-goma-rbe-ats-latest': 'release_bot',
       'linux-archive-rel-goma-rbe-canary': 'release_bot',
-      'linux-archive-rel-goma-rbe-latest': 'release_bot',
 
       'mac-archive-rel-goma-rbe-canary': 'release_bot_mac_strip_minimal_symbols',
-      'mac-archive-rel-goma-rbe-latest': 'release_bot_mac_strip_minimal_symbols',
     },
 
     'chromium.gpu': {
diff --git a/tools/mb/mb_config_expectations/chromium.goma.fyi.json b/tools/mb/mb_config_expectations/chromium.goma.fyi.json
index 573c890..d6065e01 100644
--- a/tools/mb/mb_config_expectations/chromium.goma.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.goma.fyi.json
@@ -9,16 +9,6 @@
       "use_goma": true
     }
   },
-  "Linux Builder Goma RBE Latest Client": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "use_goma": true
-    }
-  },
   "Mac Builder (dbg) Goma RBE Canary (clobber)": {
     "gn_args": {
       "ffmpeg_branding": "Chrome",
@@ -29,16 +19,6 @@
       "use_goma": true
     }
   },
-  "Mac Builder (dbg) Goma RBE Latest Client (clobber)": {
-    "gn_args": {
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": true,
-      "is_debug": true,
-      "proprietary_codecs": true,
-      "symbol_level": 1,
-      "use_goma": true
-    }
-  },
   "Mac M1 Builder (dbg) Goma RBE Canary (clobber)": {
     "gn_args": {
       "ffmpeg_branding": "Chrome",
@@ -61,17 +41,6 @@
       "use_goma": true
     }
   },
-  "Win Builder (dbg) Goma RBE ATS Latest Client": {
-    "gn_args": {
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": true,
-      "is_debug": true,
-      "proprietary_codecs": true,
-      "symbol_level": 0,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
   "Win Builder (dbg) Goma RBE Canary": {
     "gn_args": {
       "ffmpeg_branding": "Chrome",
@@ -83,17 +52,6 @@
       "use_goma": true
     }
   },
-  "Win Builder (dbg) Goma RBE Latest Client": {
-    "gn_args": {
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": true,
-      "is_debug": true,
-      "proprietary_codecs": true,
-      "symbol_level": 0,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
   "Win Builder Goma RBE ATS Canary": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -106,18 +64,6 @@
       "use_goma": true
     }
   },
-  "Win Builder Goma RBE ATS Latest Client": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
   "Win Builder Goma RBE Canary": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -130,30 +76,6 @@
       "use_goma": true
     }
   },
-  "Win Builder Goma RBE Canary (clobber)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
-  "Win Builder Goma RBE Latest Client": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
   "android-archive-dbg-goma-rbe-ats-canary": {
     "gn_args": {
       "debuggable_apks": false,
@@ -164,16 +86,6 @@
       "use_goma": true
     }
   },
-  "android-archive-dbg-goma-rbe-ats-latest": {
-    "gn_args": {
-      "debuggable_apks": false,
-      "is_component_build": true,
-      "is_debug": true,
-      "symbol_level": 1,
-      "target_os": "android",
-      "use_goma": true
-    }
-  },
   "android-archive-dbg-goma-rbe-canary": {
     "gn_args": {
       "debuggable_apks": false,
@@ -184,16 +96,6 @@
       "use_goma": true
     }
   },
-  "android-archive-dbg-goma-rbe-latest": {
-    "gn_args": {
-      "debuggable_apks": false,
-      "is_component_build": true,
-      "is_debug": true,
-      "symbol_level": 1,
-      "target_os": "android",
-      "use_goma": true
-    }
-  },
   "chromeos-amd64-generic-rel-goma-rbe-canary": {
     "args_file": "//build/args/chromeos/amd64-generic-vm.gni",
     "gn_args": {
@@ -206,18 +108,6 @@
       "use_remoteexec": false
     }
   },
-  "chromeos-amd64-generic-rel-goma-rbe-latest": {
-    "args_file": "//build/args/chromeos/amd64-generic-vm.gni",
-    "gn_args": {
-      "also_build_lacros_chrome_for_architecture": "amd64",
-      "dcheck_always_on": false,
-      "is_chromeos_device": true,
-      "ozone_platform_headless": true,
-      "use_goma": true,
-      "use_real_dbus_clients": false,
-      "use_remoteexec": false
-    }
-  },
   "ios-device-goma-rbe-canary-clobber": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -232,20 +122,6 @@
       "use_goma": true
     }
   },
-  "ios-device-goma-rbe-latest-clobber": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ios_code_signing_identity_description": "Apple Development",
-      "ios_enable_code_signing": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 0,
-      "target_cpu": "arm64",
-      "target_environment": "device",
-      "target_os": "ios",
-      "use_goma": true
-    }
-  },
   "linux-archive-rel-goma-rbe-ats-canary": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -254,14 +130,6 @@
       "use_goma": true
     }
   },
-  "linux-archive-rel-goma-rbe-ats-latest": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
   "linux-archive-rel-goma-rbe-canary": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -270,14 +138,6 @@
       "use_goma": true
     }
   },
-  "linux-archive-rel-goma-rbe-latest": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
   "mac-archive-rel-goma-rbe-canary": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -287,15 +147,5 @@
       "symbol_level": 1,
       "use_goma": true
     }
-  },
-  "mac-archive-rel-goma-rbe-latest": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "enable_stripping": true,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 1,
-      "use_goma": true
-    }
   }
 }
\ No newline at end of file
diff --git a/tools/mb/mb_config_expectations/chromium.goma.json b/tools/mb/mb_config_expectations/chromium.goma.json
index 5b4f4b91..a61015b 100644
--- a/tools/mb/mb_config_expectations/chromium.goma.json
+++ b/tools/mb/mb_config_expectations/chromium.goma.json
@@ -13,34 +13,6 @@
       "use_goma": true
     }
   },
-  "Chromium Android ARM 32-bit Goma RBE ToT": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "debuggable_apks": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "strip_debug_info": true,
-      "symbol_level": 1,
-      "target_os": "android",
-      "use_goma": true
-    }
-  },
-  "Chromium Android ARM 32-bit Goma RBE ToT (ATS)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "debuggable_apks": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "strip_debug_info": true,
-      "symbol_level": 1,
-      "target_os": "android",
-      "use_goma": true
-    }
-  },
   "Chromium Linux Goma RBE Staging": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -49,14 +21,6 @@
       "use_goma": true
     }
   },
-  "Chromium Linux Goma RBE Staging (clobber)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
   "Chromium Linux Goma RBE Staging (dbg)": {
     "gn_args": {
       "is_component_build": true,
@@ -65,30 +29,6 @@
       "use_goma": true
     }
   },
-  "Chromium Linux Goma RBE Staging (dbg) (clobber)": {
-    "gn_args": {
-      "is_component_build": true,
-      "is_debug": true,
-      "symbol_level": 1,
-      "use_goma": true
-    }
-  },
-  "Chromium Linux Goma RBE ToT": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
-  "Chromium Linux Goma RBE ToT (ATS)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
   "Chromium Mac Goma RBE Staging": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -97,14 +37,6 @@
       "use_goma": true
     }
   },
-  "Chromium Mac Goma RBE Staging (clobber)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
   "Chromium Mac Goma RBE Staging (dbg)": {
     "gn_args": {
       "is_component_build": true,
@@ -113,14 +45,6 @@
       "use_goma": true
     }
   },
-  "Chromium Mac Goma RBE ToT": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "use_goma": true
-    }
-  },
   "Chromium Win Goma RBE ATS Staging": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -131,26 +55,6 @@
       "use_goma": true
     }
   },
-  "Chromium Win Goma RBE ATS Staging (clobber)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
-  "Chromium Win Goma RBE ATS ToT": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
   "Chromium Win Goma RBE Staging": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -161,40 +65,6 @@
       "use_goma": true
     }
   },
-  "Chromium Win Goma RBE Staging (clobber)": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
-  "Chromium Win Goma RBE ToT": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 1,
-      "target_cpu": "x86",
-      "use_goma": true
-    }
-  },
-  "Chromium iOS Goma RBE ToT": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ios_code_signing_identity_description": "Apple Development",
-      "ios_enable_code_signing": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 0,
-      "target_cpu": "arm64",
-      "target_environment": "device",
-      "target_os": "ios",
-      "use_goma": true
-    }
-  },
   "chromeos-amd64-generic-rel-goma-rbe-staging": {
     "args_file": "//build/args/chromeos/amd64-generic-vm.gni",
     "gn_args": {
@@ -206,17 +76,5 @@
       "use_real_dbus_clients": false,
       "use_remoteexec": false
     }
-  },
-  "chromeos-amd64-generic-rel-goma-rbe-tot": {
-    "args_file": "//build/args/chromeos/amd64-generic-vm.gni",
-    "gn_args": {
-      "also_build_lacros_chrome_for_architecture": "amd64",
-      "dcheck_always_on": false,
-      "is_chromeos_device": true,
-      "ozone_platform_headless": true,
-      "use_goma": true,
-      "use_real_dbus_clients": false,
-      "use_remoteexec": false
-    }
   }
 }
\ No newline at end of file
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ba845fe..db5cd38 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1092,6 +1092,7 @@
   <int value="5" label="Manual password generation"/>
   <int value="6" label="Save passwords toggle"/>
   <int value="7" label="'Show other passwords' link"/>
+  <int value="8" label="Show Passkeys again"/>
 </enum>
 
 <enum name="AccessoryBarContents">
@@ -1378,6 +1379,7 @@
   <int value="2" label="Directions"/>
   <int value="3" label="Call"/>
   <int value="6" label="Website"/>
+  <int value="20" label="Reviews"/>
 </enum>
 
 <enum name="ActionUponResourceRequest">
@@ -5598,6 +5600,13 @@
   <int value="9" label="STATUS_DBUS_ERROR"/>
 </enum>
 
+<enum name="AttributionDestinationThrottlerResult">
+  <int value="0" label="Allowed"/>
+  <int value="1" label="Disallowed (global limit)"/>
+  <int value="2" label="Disallowed (reporting site limit)"/>
+  <int value="3" label="Disallowed (both limits)"/>
+</enum>
+
 <enum name="AttributionNavigationType">
   <int value="0" label="Anchor"/>
   <int value="1" label="window.open"/>
@@ -10613,19 +10622,19 @@
   <int value="2" label="(02) AR_DELEGATE"/>
   <int value="3" label="(03) SCENE_OVERLAY"/>
   <int value="4" label="(04) START_SURFACE (with tab switcher if enabled)"/>
-  <int value="5" label="(05) SELECTION_POPUP"/>
-  <int value="6" label="(06) MANUAL_FILLING"/>
-  <int value="7" label="(07) FULLSCREEN"/>
-  <int value="8" label="(08) BOTTOM_SHEET"/>
-  <int value="9" label="(10) TAB_MODAL_HANDLER"/>
-  <int value="10" label="(11) TAB_SWITCHER (if Start surface is disabled)"/>
+  <int value="5" label="(06) SELECTION_POPUP"/>
+  <int value="6" label="(07) MANUAL_FILLING"/>
+  <int value="7" label="(08) FULLSCREEN"/>
+  <int value="8" label="(09) BOTTOM_SHEET"/>
+  <int value="9" label="(11) TAB_MODAL_HANDLER"/>
+  <int value="10" label="(05) TAB_SWITCHER (if Start surface is disabled)"/>
   <int value="11" label="(12) CLOSE_WATCHER"/>
   <int value="12" label="(14) TAB_HISTORY (and bottom controls)"/>
   <int value="13" label="(15) TAB_RETURN_TO_CHROME_START_SURFACE"/>
   <int value="14" label="(16) SHOW_READING_LIST"/>
   <int value="15" label="(17) MINIMIZE_APP_AND_CLOSE_TAB"/>
   <int value="16" label="(13) FIND_TOOLBAR"/>
-  <int value="17" label="(09) LOCATION_BAR"/>
+  <int value="17" label="(10) LOCATION_BAR"/>
   <int value="18" label="(02) XR_DELEGATE"/>
 </enum>
 
@@ -18156,13 +18165,6 @@
   <int value="3" label="Header set by client code"/>
 </enum>
 
-<enum name="Companion.OpenTrigger">
-  <int value="0" label="Unknown"/>
-  <int value="1" label="Context menu image search"/>
-  <int value="2" label="Context menu text search"/>
-  <int value="3" label="Other"/>
-</enum>
-
 <enum name="Companion.PhFeedback">
   <int value="1" label="Thumbs up"/>
   <int value="2" label="Thumbs down"/>
@@ -19731,7 +19733,7 @@
   <int value="1" label="Internal error"/>
   <int value="2" label="No capacity for conversion destination"/>
   <int value="3" label="No matching impressions"/>
-  <int value="4" label="Rate-limited"/>
+  <int value="4" label="Excessive attributions"/>
   <int value="5" label="Excessive reporting origins"/>
   <int value="6" label="No histograms created"/>
   <int value="7" label="Insufficient aggregatable budget"/>
@@ -19798,7 +19800,7 @@
   <int value="3" label="No capacity for conversion destination"/>
   <int value="4" label="No matching impressions"/>
   <int value="5" label="Deduplicated against existing report"/>
-  <int value="6" label="Rate-limited"/>
+  <int value="6" label="Excessive attributions"/>
   <int value="7" label="Priority lower than existing reports"/>
   <int value="8" label="Dropped for noise"/>
   <int value="9" label="Excessive reporting origins"/>
@@ -60006,6 +60008,8 @@
   <int value="-1330409814" label="ContextMenuCopyImage:enabled"/>
   <int value="-1329990210"
       label="UserCloudSigninRestrictionPolicyFetcher:disabled"/>
+  <int value="-1329973081"
+      label="CrOSLateBootArcVmAAudioMMAPLowLatency:enabled"/>
   <int value="-1329586063"
       label="MigrateDefaultChromeAppToWebAppsNonGSuite:disabled"/>
   <int value="-1328090640" label="DesktopPWAsAppHomePage:enabled"/>
@@ -60573,6 +60577,7 @@
       label="EnableWebUsbOnExtensionServiceWorker:enabled"/>
   <int value="-1037128156" label="HomeLauncherGestures:disabled"/>
   <int value="-1035705204" label="PwaUpdateDialogForName:disabled"/>
+  <int value="-1035498688" label="TabStripStartupRefactoring:disabled"/>
   <int value="-1035346097" label="SyncErrorInfoBarAndroid:enabled"/>
   <int value="-1035140982" label="ClientSideDetectionModelOnAndroid:enabled"/>
   <int value="-1034344165" label="V8NoTurbo:disabled"/>
@@ -65366,6 +65371,8 @@
   <int value="1553777815" label="enable-search-prefetch-service"/>
   <int value="1556554961" label="DriveFsBidirectionalNativeMessaging:enabled"/>
   <int value="1556739957" label="Jelly:disabled"/>
+  <int value="1556835732"
+      label="CrOSLateBootArcVmAAudioMMAPLowLatency:disabled"/>
   <int value="1557680135" label="PartitionedCookies:disabled"/>
   <int value="1558410842" label="FilesZipNoNaCl:disabled"/>
   <int value="1558757619"
@@ -65569,6 +65576,7 @@
   <int value="1665349789" label="spurious-power-button-window"/>
   <int value="1665400247" label="OsSettingsDeepLinking:enabled"/>
   <int value="1665430464" label="force-control-face-ae"/>
+  <int value="1666933104" label="TabStripStartupRefactoring:enabled"/>
   <int value="1667533501" label="SameAppWindowCycle:disabled"/>
   <int value="1667584730" label="WebXR:disabled"/>
   <int value="1667886516"
@@ -68081,6 +68089,11 @@
   <int value="754" label="scroll-start-x"/>
   <int value="755" label="scroll-start-y"/>
   <int value="756" label="scroll-start"/>
+  <int value="757" label="scroll-start-target-block"/>
+  <int value="758" label="scroll-start-target-inline"/>
+  <int value="759" label="scroll-start-target-x"/>
+  <int value="760" label="scroll-start-target-y"/>
+  <int value="761" label="scroll-start-target"/>
 </enum>
 
 <enum name="MappedEditingCommands">
@@ -71729,6 +71742,12 @@
       label="Histogram active sample wrapped 2^31 during accumulation."/>
 </enum>
 
+<enum name="NegotiatedHttpDatagramVersion">
+  <int value="0" label="Not supported"/>
+  <int value="1" label="draft-04"/>
+  <int value="2" label="rfc"/>
+</enum>
+
 <enum name="NeighborLinkMonitorFailureType">
   <int value="0" label="Unknown type of failure"/>
   <int value="1" label="IPv4 gateway neighbor lost"/>
@@ -75241,6 +75260,7 @@
   <int value="0" label="Visit"/>
   <int value="1" label="Suggest"/>
   <int value="2" label="ShowAll"/>
+  <int value="3" label="Cart"/>
 </enum>
 
 <enum name="NTPHistoryClustersImageDisplayState">
@@ -77765,6 +77785,7 @@
   <int value="1117" label="Privacy: Microphone Software Switch"/>
   <int value="1118" label="Privacy: Geolocation Software Switch"/>
   <int value="1119" label="Privacy: Lock Screen Notification Switch"/>
+  <int value="1120" label="Privacy: Speak-on-mute Detection Software Switch"/>
   <int value="1200" label="Add Language"/>
   <int value="1201" label="Show Input Options In Shelf"/>
   <int value="1202" label="Show Personal Information Suggestions (Deprecated)"/>
@@ -79092,35 +79113,6 @@
   <int value="4" label="Both candidates match initiator"/>
 </enum>
 
-<enum name="PaintArtifactCompositorUpdateReason">
-  <int value="0" label="Test"/>
-  <int value="1" label="PaintArtifactCompositorNeedsFullUpdateChunksChanged"/>
-  <int value="2"
-      label="PaintArtifactCompositorNeedsFullUpdateAfterPaintingChunk"/>
-  <int value="3" label="PaintArtifactCompositorPrefersLCDText"/>
-  <int value="4" label="LocalFrameViewUpdateLayerDebugInfo"/>
-  <int value="5" label="LocalFrameViewBenchmarking"/>
-  <int value="6" label="DisplayLockContextNeedsPaintArtifactCompositorUpdate"/>
-  <int value="7" label="ViewTransitionNotifyChanges"/>
-  <int value="8" label="FrameCaretSetVisible"/>
-  <int value="9" label="FrameCaretPaint"/>
-  <int value="10" label="InspectorOverlayAgentDisableFrameOverlay"/>
-  <int value="11" label="LinkHighlightImplNeedsCompositingUpdate"/>
-  <int value="12" label="PaintLayerScrollableAreaUpdateScrollOffset"/>
-  <int value="13" label="PaintPropertyTreeBuilderPaintPropertyChanged"/>
-  <int value="14" label="PaintPropertyTreeBuilderHasFixedPositionObjects"/>
-  <int value="15" label="PaintPropertyTreeBulderNonStackingContextScroll"/>
-  <int value="16" label="VisualViewportPaintPropertyTreeBuilderUpdate"/>
-  <int value="17" label="VideoPainterPaintReplaced"/>
-  <int value="18"
-      label="PaintPropertyTreeBuilderPaintPropertyChangedOnlyNonRerasterValues"/>
-  <int value="19"
-      label="PaintPropertyTreeBuilderPaintPropertyChangedOnlySimpleValues"/>
-  <int value="20"
-      label="PaintPropertyTreeBuilderPaintPropertyChangedOnlyValues"/>
-  <int value="21" label="PaintPropertyTreeBuilderPaintPropertyAddedOrRemoved"/>
-</enum>
-
 <enum name="PaintHoldingCommitTrigger">
   <int value="0" label="Feature Disabled"/>
   <int value="1" label="Disallowed"/>
@@ -81359,6 +81351,16 @@
   <int value="3" label="Foreground XFA (XFAF)"/>
 </enum>
 
+<enum name="PdfOcrUserSelection">
+  <int value="0" label="Turned on once from the context menu"/>
+  <int value="1" label="Turned on always from the context menu"/>
+  <int value="2" label="Turned off from the context menu"/>
+  <int value="3" label="Turned on always from the more actions"/>
+  <int value="4" label="Turned off from the more actions"/>
+  <int value="5" label="Turned on always from the settings"/>
+  <int value="6" label="Turned off from the settings"/>
+</enum>
+
 <enum name="PDFVersion">
   <int value="0" label="Unknown"/>
   <int value="1" label="PDF 1.0"/>
@@ -82644,6 +82646,7 @@
   <int value="8" label="Pin Required"/>
   <int value="9" label="Pin Blocked"/>
   <int value="10" label="Invalid APN"/>
+  <int value="11" label="Internal Error"/>
 </enum>
 
 <enum name="PlatformFileError">
@@ -86024,6 +86027,7 @@
   <int value="31" label="DEVELOPER_LOW"/>
   <int value="32" label="DEVELOPER_EMPTY"/>
   <int value="33" label="INTERPRETER_RESOURCE_UNAVAILABLE"/>
+  <int value="34" label="CUPS_PKI_EXPIRED"/>
 </enum>
 
 <enum name="PrintJobResult">
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index 8a8455dd..b245d9e4 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -1606,6 +1606,27 @@
   </summary>
 </histogram>
 
+<histogram name="Accessibility.PdfOcr.ActiveWhenInaccessiblePdfOpened"
+    enum="BooleanExists" expires_after="2024-02-01">
+  <owner>kyungjunlee@google.com</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    When an inaccessible PDF is loaded, true if the PDF OCR feature was turned
+    on to extract text from images. Note that, as long as the PDF OCR is on, a
+    PDF containing images will record true even if the images contain no text.
+  </summary>
+</histogram>
+
+<histogram name="Accessibility.PdfOcr.UserSelection" enum="PdfOcrUserSelection"
+    expires_after="2024-02-01">
+  <owner>kyungjunlee@google.com</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    Tracks the PDF OCR menu item that the user selects to turn on or off the PDF
+    OCR feature.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.Performance.AXTree.Destroy" units="ms"
     expires_after="2023-10-08">
   <owner>agarwaltushar@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 3c8d3227..9b0e4b9 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -1566,6 +1566,18 @@
 </histogram>
 
 <histogram
+    name="Ash.CaptureModeController.DemoToolsEnabledOnRecordingStart.{TabletOrClamshell}"
+    enum="BooleanEnabled" expires_after="2024-01-12">
+  <owner>michelefan@chromium.org</owner>
+  <owner>gzadina@google.com</owner>
+  <summary>
+    Records whether a capture mode video recording starts with demo tools
+    feature enabled or not in {TabletOrClamshell}.
+  </summary>
+  <token key="TabletOrClamshell" variants="DisplayModes"/>
+</histogram>
+
+<histogram
     name="Ash.CaptureModeController.EndRecordingReason.{TabletOrClamshell}"
     enum="EndRecordingReason" expires_after="2023-08-01">
   <owner>afakhry@chromium.org</owner>
@@ -1669,6 +1681,18 @@
 </histogram>
 
 <histogram
+    name="Ash.CaptureModeController.Projector.DemoToolsEnabledOnRecordingStart.{TabletOrClamshell}"
+    enum="BooleanEnabled" expires_after="2024-01-12">
+  <owner>michelefan@chromium.org</owner>
+  <owner>gzadina@google.com</owner>
+  <summary>
+    Records whether a Projector-initiated recording starts with demo tools
+    feature enabled or not in {TabletOrClamshell}.
+  </summary>
+  <token key="TabletOrClamshell" variants="DisplayModes"/>
+</histogram>
+
+<histogram
     name="Ash.CaptureModeController.Projector.RecordingStartsWithCamera.{TabletOrClamshell}"
     enum="BooleanEnabled" expires_after="2023-10-17">
   <owner>michelefan@chromium.org</owner>
@@ -1681,18 +1705,6 @@
 </histogram>
 
 <histogram
-    name="Ash.CaptureModeController.Projector.RecordingStartsWithDemoTools.{TabletOrClamshell}"
-    enum="BooleanEnabled" expires_after="2024-01-12">
-  <owner>michelefan@chromium.org</owner>
-  <owner>gzadina@google.com</owner>
-  <summary>
-    Records whether a Projector-initiated recording starts with demo tools
-    feature enabled or not in {TabletOrClamshell}.
-  </summary>
-  <token key="TabletOrClamshell" variants="DisplayModes"/>
-</histogram>
-
-<histogram
     name="Ash.CaptureModeController.Projector.ScreenRecordingLength.{TabletOrClamshell}"
     units="seconds" expires_after="2023-10-17">
   <owner>afakhry@chromium.org</owner>
@@ -1752,18 +1764,6 @@
   <token key="TabletOrClamshell" variants="DisplayModes"/>
 </histogram>
 
-<histogram
-    name="Ash.CaptureModeController.RecordingStartsWithDemoTools.{TabletOrClamshell}"
-    enum="BooleanEnabled" expires_after="2024-01-12">
-  <owner>michelefan@chromium.org</owner>
-  <owner>gzadina@google.com</owner>
-  <summary>
-    Records whether a capture mode video recording starts with demo tools
-    feature enabled or not in {TabletOrClamshell}.
-  </summary>
-  <token key="TabletOrClamshell" variants="DisplayModes"/>
-</histogram>
-
 <histogram name="Ash.CaptureModeController.SaveLocation.{TabletOrClamshell}"
     enum="CaptureModeSaveToLocation" expires_after="2023-08-01">
   <owner>michelefan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 916d672a3..df5704082 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -2616,26 +2616,6 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.Paint.PaintArtifactCompositorUpdateFirstReason"
-    enum="PaintArtifactCompositorUpdateReason" expires_after="2023-06-25">
-  <owner>pdr@chromium.org</owner>
-  <owner>rego@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Records the first reason to set PaintArtifactCompositor as needing update.
-  </summary>
-</histogram>
-
-<histogram name="Blink.Paint.PaintArtifactCompositorUpdateReason"
-    enum="PaintArtifactCompositorUpdateReason" expires_after="2023-06-18">
-  <owner>pdr@chromium.org</owner>
-  <owner>rego@chromium.org</owner>
-  <owner>paint-dev@chromium.org</owner>
-  <summary>
-    Records the reason to set PaintArtifactCompositor as needing update.
-  </summary>
-</histogram>
-
 <histogram base="true" name="Blink.Paint.UpdateTime" units="microseconds"
     expires_after="2023-10-01">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
diff --git a/tools/metrics/histograms/metadata/chrome/histograms.xml b/tools/metrics/histograms/metadata/chrome/histograms.xml
index c6792bbe..07b0356 100644
--- a/tools/metrics/histograms/metadata/chrome/histograms.xml
+++ b/tools/metrics/histograms/metadata/chrome/histograms.xml
@@ -103,7 +103,7 @@
 </histogram>
 
 <histogram name="Chrome.KAnonymityService.TrustTokenGetter.Action"
-    enum="KAnonymityTrustTokenGetterAction" expires_after="M115">
+    enum="KAnonymityTrustTokenGetterAction" expires_after="M117">
   <owner>behamilton@google.com</owner>
   <owner>pauljensen@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/dev/histograms.xml b/tools/metrics/histograms/metadata/dev/histograms.xml
index 96b2368..d4b1fff 100644
--- a/tools/metrics/histograms/metadata/dev/histograms.xml
+++ b/tools/metrics/histograms/metadata/dev/histograms.xml
@@ -456,10 +456,9 @@
 </histogram>
 
 <histogram name="DevTools.SidebarPaneShown" enum="DevToolsSidebarPane"
-    expires_after="2023-08-08">
-  <owner>changhaohan@chromium.org</owner>
-  <owner>yangguo@chromium.org</owner>
+    expires_after="2023-12-31">
   <owner>bmeurer@chromium.org</owner>
+  <owner>changhaohan@chromium.org</owner>
   <summary>Specified DevTools sidebar pane was shown.</summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 41da1a5..63d9935 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -1433,10 +1433,9 @@
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     Whether an AudioOutputController succeeded in creating and opening an output
-    stream proxy. Only logged for the initial creation, subsequent re-creations
-    due to device change events are logged in the
-    Media.AudioOutputController.ProxyStreamCreationResultForDeviceChange
-    histogram.
+    stream proxy after receiving a default device change event. Note: in
+    September 2021 this histogram unintentionally swapped its meaning with
+    Media.AudioOutputController.ProxyStreamCreationResultForDeviceChange.
   </summary>
 </histogram>
 
@@ -1448,7 +1447,11 @@
   <owner>webrtc-audio-uma@google.com</owner>
   <summary>
     Whether an AudioOutputController succeeded in creating and opening an output
-    stream proxy after receiving a default device change event.
+    stream proxy. Only logged for the initial creation, subsequent re-creations
+    due to device change events are logged in the
+    Media.AudioOutputController.ProxyStreamCreationResult histogram. Note: in
+    September 2021 this histogram unintentionally swapped its meaning with
+    Media.AudioOutputController.ProxyStreamCreationResult.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 0e916138..53b87968 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -1819,6 +1819,22 @@
 </histogram>
 
 <histogram
+    name="Prerender.Experimental.PrerenderNavigationRequestNetworkErrorCode{PrerenderTriggerType}"
+    enum="NetErrorCodes" expires_after="2023-08-27">
+  <owner>lingqi@chromium.org</owner>
+  <owner>nhiroki@chromium.org</owner>
+  <owner>chrome-prerendering@google.com</owner>
+  <summary>
+    Recorded by PrerenderHost::DidFinishNavigation when a failure navigation is
+    committed to a prerendering pages's main frame.
+
+    This metric is used to monitor the detailed reasons of
+    PrerenderHostFinalStatus::kNavigationRequestNetworkError.
+  </summary>
+  <token key="PrerenderTriggerType" variants="PrerenderTriggerType"/>
+</histogram>
+
+<histogram
     name="Prerender.Experimental.Search.FirstCorrectPrerenderHintReceivedToRealSearchNavigationStartedDuration"
     units="ms" expires_after="2023-08-20">
   <owner>lingqi@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index f536681..60da652 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -5559,6 +5559,16 @@
   <summary>All HTTP status codes seen during WebSocket handshakes.</summary>
 </histogram>
 
+<histogram name="Net.WebTransport.NegotiatedHttpDatagramVersion"
+    enum="NegotiatedHttpDatagramVersion" expires_after="2023-11-12">
+  <owner>bashi@chromium.org</owner>
+  <owner>blink-network-stack@google.com</owner>
+  <summary>
+    Negotiated HTTP Datagram version. Recorded when a WebTransport session
+    becomes ready.
+  </summary>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index b3af776..cbe57b06 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -639,6 +639,18 @@
   </summary>
 </histogram>
 
+<histogram name="NewTabPage.Drive.FileCount" units="count"
+    expires_after="never">
+<!-- expires-never: Used for Chirp monitoring. -->
+
+  <owner>tiborg@chromium.org</owner>
+  <owner>chrome-desktop-ntp@google.com</owner>
+  <summary>
+    Logs the number of files returned by ItemSuggest. Logged every time the
+    Drive module successfully processed an ItemSuggest response.
+  </summary>
+</histogram>
+
 <histogram name="NewTabPage.Drive.ItemSuggestRequestResult"
     enum="ItemSuggestRequestResult" expires_after="never">
 <!-- expires-never: Used for Chirp monitoring. -->
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index ffd6aa51..f77cf43 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -254,6 +254,28 @@
   </summary>
 </histogram>
 
+<histogram name="AccessCodeCast.Session.FreezeCount" units="instances"
+    expires_after="2023-12-01">
+  <owner>bzielinski@google.com</owner>
+  <owner>cros-edu-eng@google.com</owner>
+  <summary>
+    The count of times that a local cast mirroring session is frozen, or paused,
+    during its duration. Recorded at the end of a cast mirroring session. Logged
+    only on desktop platforms.
+  </summary>
+</histogram>
+
+<histogram name="AccessCodeCast.Session.FreezeDuration" units="ms"
+    expires_after="2023-12-01">
+  <owner>bzielinski@google.com</owner>
+  <owner>cros-edu-eng@google.com</owner>
+  <summary>
+    The amount of time in milliseconds that a local cast mirroring session is
+    frozen, or paused, until it is resumed or ended. Recorded when the session
+    is resumed, or when the session ends. Logged only on desktop platforms.
+  </summary>
+</histogram>
+
 <histogram name="AccessCodeCast.Session.RouteDiscoveryTypeAndSource"
     enum="AccessCodeCastDiscoveryTypeAndSource" expires_after="2023-08-20">
   <owner>bzielinski@google.com</owner>
@@ -3694,6 +3716,17 @@
   </summary>
 </histogram>
 
+<histogram name="Conversions.DestinationThrottlerResult"
+    enum="AttributionDestinationThrottlerResult" expires_after="2023-10-08">
+  <owner>csharrison@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    For every successfully parsed attribution source that is also allowed by
+    browser policy, records the result of the time-based destination limit,
+    which governs whether a source can proceed to the storage layer.
+  </summary>
+</histogram>
+
 <histogram name="Conversions.EnqueueEventAllowed" enum="BooleanAllowed"
     expires_after="2023-10-08">
   <owner>apaseltiner@chromium.org</owner>
@@ -4025,17 +4058,6 @@
   </summary>
 </histogram>
 
-<histogram name="Conversions.SourceAllowedByDestinationWindowLimit"
-    enum="BooleanAllowed" expires_after="2023-10-08">
-  <owner>csharrison@chromium.org</owner>
-  <owner>measurement-api-dev+metrics@google.com</owner>
-  <summary>
-    For every successfully parsed attribution source that is also allowed by
-    browser policy, records true if the time-based destination limit allows the
-    source to proceed to the storage layer.
-  </summary>
-</histogram>
-
 <histogram name="Conversions.SourceRegistration.NavigationType.{RequestType}"
     enum="AttributionNavigationType" expires_after="M117">
   <owner>csharrison@chromium.org</owner>
@@ -12186,7 +12208,7 @@
 </histogram>
 
 <histogram name="SpellCheck.SpellingService.RequestHttpResponseCode"
-    enum="HttpResponseCode" expires_after="2023-05-07">
+    enum="HttpResponseCode" expires_after="2023-08-07">
   <owner>yyushkina@google.com</owner>
   <owner>gujen@google.com</owner>
   <owner>chrome-language@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 8986332..c4f2e73 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -1670,16 +1670,6 @@
   </summary>
 </histogram>
 
-<histogram name="PasswordManager.FormSubmission.PerProfileType"
-    enum="BrowserProfileType" expires_after="2023-02-02">
-  <owner>roagarwal@chromium.org</owner>
-  <owner>chrome-incognito@google.com</owner>
-  <summary>
-    This histogram records the browser profile type when a password form is
-    submitted.
-  </summary>
-</histogram>
-
 <histogram name="PasswordManager.FormVisited.PerProfileType"
     enum="BrowserProfileType" expires_after="2023-08-27">
   <owner>rhalavati@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 02e11fb..e38dcac 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -2319,6 +2319,16 @@
   </summary>
 </histogram>
 
+<histogram name="SafeBrowsing.V4Database.DirectoryCreationResult"
+    enum="PlatformFileError" expires_after="2023-09-10">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-counter-abuse-alerts@google.com</owner>
+  <summary>
+    Records the result of trying to create the &quot;Safe Browsing&quot;
+    directory for storing the V4 database. Logged once on startup.
+  </summary>
+</histogram>
+
 <histogram name="SafeBrowsing.V4Database.Size" units="KB"
     expires_after="2023-09-17">
   <owner>vakh@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index c84d097e..b777e47 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -4031,6 +4031,38 @@
   </metric>
 </event>
 
+<event name="Blink.JavaScriptFramework.Versions" singular="True">
+  <owner>houssein@chromium.org</owner>
+  <owner>nrosenthal@chromium.org</owner>
+  <summary>
+    Metrics stating the detected major+minor version of known frameworks when
+    detected. This event is recorded when the page is loaded. Note that a page
+    can use multiple frameworks at the same time, hence why this reports each
+    framework individually instead of a single number. The version numbers here
+    are a bit mask: 0xMMmm (Major+minor), e.g. version 16.3 would be 0x1003
+  </summary>
+  <metric name="AngularVersion">
+    <summary>
+      A bitmask (0xMMmm) representing the Angular version
+    </summary>
+  </metric>
+  <metric name="NextJSVersion">
+    <summary>
+      A bitmask (0xMMmm) representing the NextJS version
+    </summary>
+  </metric>
+  <metric name="NuxtVersion">
+    <summary>
+      A bitmask (0xMMmm) representing the Nuxt version
+    </summary>
+  </metric>
+  <metric name="VueVersion">
+    <summary>
+      A bitmask (0xMMmm) representing the Vue version
+    </summary>
+  </metric>
+</event>
+
 <event name="Blink.PageLoad">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -6714,7 +6746,7 @@
       eligibility check through text finder API. Clamped to a max value of 10.
     </summary>
   </metric>
-  <metric name="OpenTrigger" enum="Companion.OpenTrigger">
+  <metric name="OpenTrigger" enum="SidePanelOpenTrigger">
     <summary>
       The UI location from which the companion page was opened.
     </summary>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index bca0ec3..d27e335 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -7,6 +7,7 @@
 UNSCHEDULED_blink_perf.performance_apis,yoavweiss@chromium.org,Blink>PerformanceAPIs,https://bit.ly/blink-perf-benchmarks,all
 UNSCHEDULED_blink_perf.service_worker,"yyanagisawa@chromium.org, chrome-worker@google.com",Blink>ServiceWorker,https://bit.ly/blink-perf-benchmarks,
 UNSCHEDULED_blink_perf.view_transitions,"bokan@chromium.org, khushalsagar@chromium.org, vmpstr@chromium.org",Blink>ViewTransitions,https://bit.ly/blink-perf-benchmarks,all
+UNSCHEDULED_dummy_wpr_benchmark.loading_using_wpr,maxqli@googl.com,Test>Telemetry,,
 UNSCHEDULED_loading.mbi,blink-isolation-dev@chromium.org,Blink>Internals>Modularization,https://bit.ly/loading-benchmarks,many_agents
 UNSCHEDULED_speedometer1.0,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,https://browserbench.org/Speedometer,all
 UNSCHEDULED_speedometer2.0,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,https://browserbench.org/Speedometer2.0,all
diff --git a/tools/perf/benchmarks/dummy_wpr_benchmark.py b/tools/perf/benchmarks/dummy_wpr_benchmark.py
new file mode 100644
index 0000000..4a0920a5
--- /dev/null
+++ b/tools/perf/benchmarks/dummy_wpr_benchmark.py
@@ -0,0 +1,38 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Dummy benchmarks using WPR to test page load time.
+
+The number produced isn't meant to represent any actual performance
+data of the browser.
+"""
+
+from benchmarks import loading_metrics_category
+from core import perf_benchmark
+from page_sets import dummy_wpr_story_set
+from telemetry import benchmark
+from telemetry.web_perf import timeline_based_measurement
+
+
+@benchmark.Info(emails=['maxqli@googl.com'],
+                component='Test>Telemetry')
+class DummyWprLoadBenchmark(perf_benchmark.PerfBenchmark):
+  options = {'pageset_repeat': 2}
+  page_set = dummy_wpr_story_set.DummyWprStorySet
+
+  def CreateCoreTimelineBasedMeasurementOptions(self):
+    tbm_options = timeline_based_measurement.Options()
+    loading_metrics_category.AugmentOptionsForLoadingMetrics(tbm_options)
+    tbm_options.config.chrome_trace_config.EnableUMAHistograms(
+        'PageLoad.PaintTiming.NavigationToLargestContentfulPaint',
+        'PageLoad.PaintTiming.NavigationToFirstContentfulPaint',
+        'PageLoad.LayoutInstability.CumulativeShiftScore')
+    return tbm_options
+
+  def CreateStorySet(self, options):
+    return dummy_wpr_story_set.DummyWprStorySet()
+
+  @classmethod
+  def Name(cls):
+    return 'UNSCHEDULED_dummy_wpr_benchmark.loading_using_wpr'
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 28534f5..ad6d161 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "perfetto-luci-artifacts/v34.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "788ddd791cec2ba6b7d8717edc6766a215144b60",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/e04127739c5e542d7db6b93cb33d908cee9983ef/trace_processor_shell.exe"
+            "hash": "f085c34f2d2f081b55de458f0e07beb94c78762e",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/f1b7df4d45aef9b9c96193319759a8c96c4533ca/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "336a42cb9ec3c417e13a97816271fec10cdf67e5",
             "full_remote_path": "perfetto-luci-artifacts/v34.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "ca3356ab3ed5dd1fb5d0a5c051eb496fdfc257d8",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/b7b22f491aa4aac9db51e34cdb53a88e57543b35/trace_processor_shell"
+            "hash": "282816767c4ef4035ff8cd3c21af2c143accca25",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/f1b7df4d45aef9b9c96193319759a8c96c4533ca/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "c32364e05e22cdf82ee0866aedd11c0e2050809c",
             "full_remote_path": "perfetto-luci-artifacts/v34.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "0b4cd25a6e5a0f698cf485ca3e8aec1fea385bc5",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/e04127739c5e542d7db6b93cb33d908cee9983ef/trace_processor_shell"
+            "hash": "912b6a52ba7675a84edec2fac3c928b962e8460a",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/f1b7df4d45aef9b9c96193319759a8c96c4533ca/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/undocumented_benchmarks.py b/tools/perf/core/undocumented_benchmarks.py
index b0ccafb5..56708437 100644
--- a/tools/perf/core/undocumented_benchmarks.py
+++ b/tools/perf/core/undocumented_benchmarks.py
@@ -17,6 +17,7 @@
     'system_health.webview_startup',
     'tab_switching.typical_25',
     'tracing_perftests',
+    'UNSCHEDULED_dummy_wpr_benchmark.loading_using_wpr',
     'v8.runtime_stats.top_25',
     'views_perftests',
     'wasmpspdfkit',
diff --git a/tools/perf/page_sets/data/dummy_wpr.json b/tools/perf/page_sets/data/dummy_wpr.json
new file mode 100644
index 0000000..b75d377
--- /dev/null
+++ b/tools/perf/page_sets/data/dummy_wpr.json
@@ -0,0 +1,9 @@
+{
+    "archives": {
+        "google_main_page": {
+            "DEFAULT": "dummy_wpr_43ed203978.wprgo"
+        }
+    },
+    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
+    "platform_specific": true
+}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dummy_wpr_43ed203978.wprgo.sha1 b/tools/perf/page_sets/data/dummy_wpr_43ed203978.wprgo.sha1
new file mode 100644
index 0000000..22f8548
--- /dev/null
+++ b/tools/perf/page_sets/data/dummy_wpr_43ed203978.wprgo.sha1
@@ -0,0 +1 @@
+43ed203978bcc7a2994b1449072e2601f4a60884
\ No newline at end of file
diff --git a/tools/perf/page_sets/dummy_wpr_story_set.py b/tools/perf/page_sets/dummy_wpr_story_set.py
new file mode 100644
index 0000000..61f00a9aa
--- /dev/null
+++ b/tools/perf/page_sets/dummy_wpr_story_set.py
@@ -0,0 +1,24 @@
+# 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.
+
+from telemetry.page import page as page_module
+from telemetry import story
+
+
+class DummyWprPage(page_module.Page):
+
+  def __init__(self, page_set):
+    super(DummyWprPage, self).__init__(
+      url='https://google.com',
+      name='google_main_page',
+      page_set=page_set)
+
+
+class DummyWprStorySet(story.StorySet):
+
+  def __init__(self):
+    super(DummyWprStorySet, self).__init__(
+      archive_data_file='data/dummy_wpr.json',
+      cloud_storage_bucket=story.PARTNER_BUCKET)
+    self.AddStory(DummyWprPage(self))
diff --git a/tools/perf/process_perf_results.pydeps b/tools/perf/process_perf_results.pydeps
index 6ec7a59..468731f1 100644
--- a/tools/perf/process_perf_results.pydeps
+++ b/tools/perf/process_perf_results.pydeps
@@ -470,6 +470,7 @@
 benchmarks/blink_perf_unittest.py
 benchmarks/desktop_ui.py
 benchmarks/dummy_benchmark.py
+benchmarks/dummy_wpr_benchmark.py
 benchmarks/jetstream2.py
 benchmarks/loading.py
 benchmarks/loading_metrics_category.py
@@ -629,6 +630,7 @@
 page_sets/desktop_ui/webui_tab_strip_story.py
 page_sets/desktop_ui/webui_utils.py
 page_sets/dummy_story_set.py
+page_sets/dummy_wpr_story_set.py
 page_sets/google_pages.py
 page_sets/helpers/__init__.py
 page_sets/helpers/override_online.py
diff --git a/tools/traffic_annotation/scripts/auditor/util.py b/tools/traffic_annotation/scripts/auditor/util.py
index 6f16d959..65f2e62 100644
--- a/tools/traffic_annotation/scripts/auditor/util.py
+++ b/tools/traffic_annotation/scripts/auditor/util.py
@@ -240,6 +240,7 @@
         Destination.WEBSITE: "Website",
         Destination.GOOGLE_OWNED_SERVICE: "Google",
         Destination.LOCAL: "Local",
+        Destination.PROXIED_GOOGLE_OWNED_SERVICE: "Proxied to Google",
         Destination.OTHER: "Other",
     }
     if (semantics.destination == Destination.OTHER
@@ -248,7 +249,9 @@
     elif semantics.destination in destination_names:
       line += "\t{}".format(destination_names[semantics.destination])
     else:
-      raise ValueError("Invalid value for the semantics.destination field")
+      raise ValueError(
+          "Invalid value for the semantics.destination field: {}".format(
+              semantics.destination))
 
     # Policy.
     policy = annotation.proto.policy
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 19f7dd4..8bf71b1 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -415,4 +415,5 @@
  <item id="promise_app_service" added_in_milestone="114" content_hash_code="07856112" os_list="chromeos" file_path="chrome/browser/apps/app_service/promise_apps/promise_app_almanac_connector.cc" />
  <item id="safe_browsing_hashprefix_realtime_lookup_direct" added_in_milestone="114" content_hash_code="0764e462" os_list="linux,windows,chromeos,android" file_path="components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.cc" />
  <item id="safe_browsing_hashprefix_realtime_lookup_ohttp" added_in_milestone="114" content_hash_code="07d60eb8" os_list="linux,windows,chromeos,android" file_path="components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.cc" />
+ <item id="gaia_auth_rotate_bound_cookies" added_in_milestone="115" content_hash_code="06d1310c" os_list="linux" file_path="chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 7cfcbed6..897de09 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -380,6 +380,7 @@
       <annotation id="gaia_auth_log_out"/>
       <annotation id="gaia_auth_merge_sessions"/>
       <annotation id="gaia_auth_revoke_token"/>
+      <annotation id="gaia_auth_rotate_bound_cookies"/>
       <annotation id="gaia_auth_multilogin"/>
       <annotation id="gaia_cookie_manager_external_cc_result"/>
       <annotation id="gaia_create_reauth_proof_token_for_parent"/>
diff --git a/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java b/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java
index 6aeb84f4a..89a3890 100644
--- a/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java
+++ b/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java
@@ -240,54 +240,6 @@
         return sAccessibilitySpeakPasswordEnabled;
     }
 
-    @VisibleForTesting
-    public static void setAccessibilityEnabledForTesting(boolean enabled) {
-        if (!sInitialized) updateAccessibilityServices();
-
-        State newState = new State(sState.isScreenReaderEnabled, sState.isTouchExplorationEnabled,
-                enabled, sState.isAccessibilityToolPresent, sState.isSpokenFeedbackServicePresent,
-                sState.isTextShowPasswordEnabled, sState.isOnlyPasswordManagersEnabled);
-
-        updateAndNotifyStateChange(newState);
-    }
-
-    @VisibleForTesting
-    public static void setEventTypeMaskForTesting(int mask) {
-        if (!sInitialized) updateAccessibilityServices();
-
-        // Explicitly set mask so events can be (ir)relevant to currently enabled service.
-        sEventTypeMask = mask;
-    }
-
-    @VisibleForTesting
-    public static void setScreenReaderEnabledForTesting(boolean enabled) {
-        if (!sInitialized) updateAccessibilityServices();
-
-        // Explicitly set screen reader enabled to given state since a real screen reader is not run
-        // during tests.
-        State newState = new State(enabled, sState.isTouchExplorationEnabled,
-                sState.isAnyAccessibilityServiceEnabled, sState.isAccessibilityToolPresent,
-                sState.isSpokenFeedbackServicePresent, sState.isTextShowPasswordEnabled,
-                sState.isOnlyPasswordManagersEnabled);
-
-        // Inform all listeners of this change.
-        updateAndNotifyStateChange(newState);
-    }
-
-    @VisibleForTesting
-    public static void setOnlyPasswordManagersEnabledForTesting(boolean enabled) {
-        if (!sInitialized) updateAccessibilityServices();
-
-        // Explicitly set state for only password managers enabled to given state since a real
-        // password manager is not run during tests.
-        State newState = new State(sState.isScreenReaderEnabled, sState.isTouchExplorationEnabled,
-                sState.isAnyAccessibilityServiceEnabled, sState.isAccessibilityToolPresent,
-                sState.isSpokenFeedbackServicePresent, sState.isTextShowPasswordEnabled, enabled);
-
-        // Inform all listeners of this change.
-        updateAndNotifyStateChange(newState);
-    }
-
     static void updateAccessibilityServices() {
         if (!sInitialized) {
             sState = new State(false, false, false, false, false, false, false);
@@ -666,4 +618,129 @@
         void onAnimatorDurationScaleChanged();
         void recordAccessibilityServiceInfoHistograms();
     }
+
+    // ForTesting methods.
+    // clang-format off
+
+    @VisibleForTesting
+    public static void setIsScreenReaderEnabledForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            enabled,
+            sState.isTouchExplorationEnabled,
+            sState.isAnyAccessibilityServiceEnabled,
+            sState.isAccessibilityToolPresent,
+            sState.isSpokenFeedbackServicePresent,
+            sState.isTextShowPasswordEnabled,
+            sState.isOnlyPasswordManagersEnabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setIsTouchExplorationEnabledForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            sState.isScreenReaderEnabled,
+            enabled,
+            sState.isAnyAccessibilityServiceEnabled,
+            sState.isAccessibilityToolPresent,
+            sState.isSpokenFeedbackServicePresent,
+            sState.isTextShowPasswordEnabled,
+            sState.isOnlyPasswordManagersEnabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setIsAnyAccessibilityServiceEnabledForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            sState.isScreenReaderEnabled,
+            sState.isTouchExplorationEnabled,
+            enabled,
+            sState.isAccessibilityToolPresent,
+            sState.isSpokenFeedbackServicePresent,
+            sState.isTextShowPasswordEnabled,
+            sState.isOnlyPasswordManagersEnabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setIsAccessibilityToolPresentForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            sState.isScreenReaderEnabled,
+            sState.isTouchExplorationEnabled,
+            sState.isAnyAccessibilityServiceEnabled,
+            enabled,
+            sState.isSpokenFeedbackServicePresent,
+            sState.isTextShowPasswordEnabled,
+            sState.isOnlyPasswordManagersEnabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setIsSpokenFeedbackServicePresentForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            sState.isScreenReaderEnabled,
+            sState.isTouchExplorationEnabled,
+            sState.isAnyAccessibilityServiceEnabled,
+            sState.isAccessibilityToolPresent,
+            enabled,
+            sState.isTextShowPasswordEnabled,
+            sState.isOnlyPasswordManagersEnabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setIsTextShowPasswordEnabledForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            sState.isScreenReaderEnabled,
+            sState.isTouchExplorationEnabled,
+            sState.isAnyAccessibilityServiceEnabled,
+            sState.isAccessibilityToolPresent,
+            sState.isSpokenFeedbackServicePresent,
+            enabled,
+            sState.isOnlyPasswordManagersEnabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setIsOnlyPasswordManagersEnabledForTesting(boolean enabled) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        State newState = new State(
+            sState.isScreenReaderEnabled,
+            sState.isTouchExplorationEnabled,
+            sState.isAnyAccessibilityServiceEnabled,
+            sState.isAccessibilityToolPresent,
+            sState.isSpokenFeedbackServicePresent,
+            sState.isTextShowPasswordEnabled,
+            enabled);
+
+        updateAndNotifyStateChange(newState);
+    }
+
+    @VisibleForTesting
+    public static void setEventTypeMaskForTesting(int mask) {
+        if (!sInitialized) updateAccessibilityServices();
+
+        // Explicitly set mask so events can be (ir)relevant to currently enabled service.
+        sEventTypeMask = mask;
+    }
+
+    // clang-format on
 }
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index 5f9ae71..f59dd94 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -1085,16 +1085,8 @@
         }
 
         if (pair.second->DoesNodeExpectSubtreeWillBeDestroyed()) {
-          // Don't fire redundant remove notification in the case where the
-          // parent will become ignored at the same time.
-          if (update_state.IsReparentedNode(node)) {
-            observer.OnSubtreeWillBeReparented(this, node);
-          } else if (node->parent() ||
-                     base::Contains(update_state.ignored_state_changed_ids,
-                                    node->parent()->id()) ||
-                     !node->parent()->IsIgnored()) {
-            observer.OnSubtreeWillBeDeleted(this, node);
-          }
+          NotifySubtreeWillBeReparentedOrDeletedExperimental(
+              node, &update_state, observer);
         }
         if (pair.second->DoesNodeExpectNodeWillBeDestroyed()) {
           table_info_map_.erase(node->id());
@@ -2001,6 +1993,29 @@
   }
 }
 
+void AXTree::NotifySubtreeWillBeReparentedOrDeletedExperimental(
+    AXNode* node,
+    const AXTreeUpdateState* update_state,
+    AXTreeObserver& observer) {
+  bool notify_reparented = update_state->IsReparentedNode(node);
+  bool notify_removed = !notify_reparented;
+  // Don't fire redundant remove notification in the case where the parent
+  // will become ignored at the same time.
+  if (notify_removed && node->parent() &&
+      base::Contains(update_state->ignored_state_changed_ids,
+                     node->parent()->id()) &&
+      !node->parent()->IsIgnored()) {
+    notify_removed = false;
+  }
+
+  if (notify_reparented) {
+    observer.OnSubtreeWillBeReparented(this, node);
+  }
+  if (notify_removed) {
+    observer.OnSubtreeWillBeDeleted(this, node);
+  }
+}
+
 void AXTree::NotifyNodeWillBeReparentedOrDeleted(
     AXNode* node,
     const AXTreeUpdateState* update_state) {
diff --git a/ui/accessibility/ax_tree.h b/ui/accessibility/ax_tree.h
index c8da956..b92128cf 100644
--- a/ui/accessibility/ax_tree.h
+++ b/ui/accessibility/ax_tree.h
@@ -304,6 +304,12 @@
       AXNode* node,
       const AXTreeUpdateState* update_state);
 
+  // Experimental version of the above method.
+  void NotifySubtreeWillBeReparentedOrDeletedExperimental(
+      AXNode* node,
+      const AXTreeUpdateState* update_state,
+      AXTreeObserver& observer);
+
   // Notify the delegate that |node| will be destroyed or reparented.
   void NotifyNodeWillBeReparentedOrDeleted(
       AXNode* node,
diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h
index e6ec1e8..ac22478 100644
--- a/ui/accessibility/platform/ax_platform_node_base.h
+++ b/ui/accessibility/platform/ax_platform_node_base.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/component_export.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 9df3df5..25606d2 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -416,6 +416,7 @@
     "//third_party/androidx:androidx_asynclayoutinflater_asynclayoutinflater_java",
     "//third_party/androidx:androidx_core_core_java",
     "//third_party/androidx:androidx_vectordrawable_vectordrawable_animated_java",
+    "//ui/accessibility:ax_base_java",
     "//ui/base/cursor/mojom:cursor_type_java",
     "//ui/base/ime/mojom:mojom_java",
     "//url:gurl_java",
diff --git a/ui/android/DEPS b/ui/android/DEPS
index 23e3b16..22d4465 100644
--- a/ui/android/DEPS
+++ b/ui/android/DEPS
@@ -13,6 +13,7 @@
   "+services/viz/public/mojom",
   "+skia/ext",
   "+third_party/skia",
+  "+ui/accessibility",
   "+ui/android/ui_android_jni_headers",
   "+ui/base",
   "+ui/compositor/compositor_lock.h",
diff --git a/ui/android/delegated_frame_host_android.cc b/ui/android/delegated_frame_host_android.cc
index 775d4f4c..187dfdf 100644
--- a/ui/android/delegated_frame_host_android.cc
+++ b/ui/android/delegated_frame_host_android.cc
@@ -121,10 +121,11 @@
     base::OnceCallback<void(const SkBitmap&)> callback) {
   DCHECK(CanCopyFromCompositingSurface());
 
+  const viz::SurfaceId surface_id(frame_sink_id_, local_surface_id_);
   std::unique_ptr<ui::WindowAndroidCompositor::ReadbackRef> readback_ref;
   if (view_->GetWindowAndroid() && view_->GetWindowAndroid()->GetCompositor()) {
     readback_ref =
-        view_->GetWindowAndroid()->GetCompositor()->TakeReadbackRef();
+        view_->GetWindowAndroid()->GetCompositor()->TakeReadbackRef(surface_id);
   }
   std::unique_ptr<viz::CopyOutputRequest> request =
       std::make_unique<viz::CopyOutputRequest>(
@@ -160,8 +161,7 @@
         gfx::Vector2d(output_size.width(), output_size.height()));
   }
 
-  host_frame_sink_manager_->RequestCopyOfOutput(
-      viz::SurfaceId(frame_sink_id_, local_surface_id_), std::move(request));
+  host_frame_sink_manager_->RequestCopyOfOutput(surface_id, std::move(request));
 }
 
 bool DelegatedFrameHostAndroid::CanCopyFromCompositingSurface() const {
diff --git a/ui/android/java/res/values/dimens.xml b/ui/android/java/res/values/dimens.xml
index f306db1..c2f3391a 100644
--- a/ui/android/java/res/values/dimens.xml
+++ b/ui/android/java/res/values/dimens.xml
@@ -7,11 +7,13 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Common text sizes -->
     <dimen name="headline_size">22sp</dimen>
+    <dimen name="headline2_size">18sp</dimen>
     <dimen name="text_size_large">16sp</dimen>
     <dimen name="text_size_medium">14sp</dimen>
     <dimen name="text_size_small">12sp</dimen>
 
     <dimen name="headline_size_leading" tools:ignore="UnusedResources">28sp</dimen>
+    <dimen name="headline2_size_leading" tools:ignore="UnusedResources">24sp</dimen>
     <dimen name="text_size_large_leading" tools:ignore="UnusedResources">24sp</dimen>
     <dimen name="text_size_medium_leading" tools:ignore="UnusedResources">20sp</dimen>
     <dimen name="text_size_small_leading" tools:ignore="UnusedResources">16sp</dimen>
diff --git a/ui/android/java/res/values/styles.xml b/ui/android/java/res/values/styles.xml
index bee93c9..c7757f1 100644
--- a/ui/android/java/res/values/styles.xml
+++ b/ui/android/java/res/values/styles.xml
@@ -89,6 +89,9 @@
     <style name="TextAppearance.HeadlineThick" parent="TextAppearance.AccentMediumStyle">
         <item name="android:textSize">@dimen/headline_size</item>
     </style>
+    <style name="TextAppearance.Headline2Thick" parent="TextAppearance.AccentMediumStyle">
+        <item name="android:textSize">@dimen/headline2_size</item>
+    </style>
     <style name="TextAppearance.TextLarge">
         <item name="android:textSize">@dimen/text_size_large</item>
     </style>
diff --git a/ui/android/java/src/org/chromium/ui/widget/TextViewWithClickableSpans.java b/ui/android/java/src/org/chromium/ui/widget/TextViewWithClickableSpans.java
index d193aed..b2b075a 100644
--- a/ui/android/java/src/org/chromium/ui/widget/TextViewWithClickableSpans.java
+++ b/ui/android/java/src/org/chromium/ui/widget/TextViewWithClickableSpans.java
@@ -15,13 +15,13 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.PopupMenu;
 
-import androidx.annotation.CallSuper;
 import androidx.annotation.VisibleForTesting;
 
+import org.chromium.ui.accessibility.AccessibilityState;
+
 /**
  * ClickableSpan isn't accessible by default, so we create a subclass
  * of TextView that tries to handle the case where a user clicks on a view
@@ -32,7 +32,6 @@
  */
 public class TextViewWithClickableSpans
         extends TextViewWithLeading implements View.OnLongClickListener {
-    private AccessibilityManager mAccessibilityManager;
     private PopupMenu mDisambiguationMenu;
 
     public TextViewWithClickableSpans(Context context) {
@@ -45,37 +44,20 @@
         init();
     }
 
-    @CallSuper
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ensureValidLongClickListenerState();
-    }
-
-    @CallSuper
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        if (visibility == View.GONE) return;
-        ensureValidLongClickListenerState();
-    }
-
     private void init() {
         // This disables the saving/restoring since the saved text may be in the wrong language
         // (if the user just changed system language), and restoring spans doesn't work anyway.
         // See crbug.com/533362
         setSaveEnabled(false);
-        mAccessibilityManager = (AccessibilityManager)
-                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        ensureValidLongClickListenerState();
+        setOnLongClickListener(this);
     }
 
     @Override
     public boolean onLongClick(View v) {
         assert v == this;
-        if (!mAccessibilityManager.isTouchExplorationEnabled()) {
-            assert false : "Long click listener should have been removed if not in"
-                           + " accessibility mode.";
+        if (!AccessibilityState.isTouchExplorationEnabled()) {
+            // If no accessibility services that requested touch exploration are enabled, then this
+            // view should not consume the long click action.
             return false;
         }
         openDisambiguationMenu();
@@ -89,11 +71,6 @@
         super.setOnLongClickListener(listener);
     }
 
-    private void ensureValidLongClickListenerState() {
-        if (mAccessibilityManager == null) return;
-        setOnLongClickListener(mAccessibilityManager.isTouchExplorationEnabled() ? this : null);
-    }
-
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
         // BrailleBack will generate an accessibility click event directly
@@ -111,7 +88,7 @@
         boolean superResult = super.onTouchEvent(event);
 
         if (event.getAction() != MotionEvent.ACTION_UP
-                && mAccessibilityManager.isTouchExplorationEnabled()
+                && AccessibilityState.isTouchExplorationEnabled()
                 && !touchIntersectsAnyClickableSpans(event)) {
             handleAccessibilityClick();
             return true;
diff --git a/ui/android/window_android_compositor.h b/ui/android/window_android_compositor.h
index a5df6abf..b522f92 100644
--- a/ui/android/window_android_compositor.h
+++ b/ui/android/window_android_compositor.h
@@ -12,6 +12,10 @@
 #include "ui/android/ui_android_export.h"
 #include "ui/compositor/compositor_lock.h"
 
+namespace viz {
+class SurfaceId;
+}
+
 namespace ui {
 
 class ResourceManager;
@@ -32,8 +36,11 @@
 
   // While there are outstanding ReadbackRefs, Compositor will attempt to
   // ensure any pending viz::CopyOutputRequest in any part of the compositor
-  // surface tree are fulfilled in a timely manner.
-  virtual std::unique_ptr<ReadbackRef> TakeReadbackRef() = 0;
+  // surface tree are fulfilled in a timely manner. `surface_id` corresponds to
+  // the `Surface` being copied. The GPU contents of this `surface_id` are kept
+  // alive as long as there is an outstanding `ReadbackRef` for it.
+  virtual std::unique_ptr<ReadbackRef> TakeReadbackRef(
+      const viz::SurfaceId& surface_id) = 0;
   virtual void RequestCopyOfOutputOnRootLayer(
       std::unique_ptr<viz::CopyOutputRequest> request) = 0;
   virtual void SetNeedsAnimate() = 0;
diff --git a/ui/base/ime/ash/input_method_ash.cc b/ui/base/ime/ash/input_method_ash.cc
index 2e5970b..829a63c 100644
--- a/ui/base/ime/ash/input_method_ash.cc
+++ b/ui/base/ime/ash/input_method_ash.cc
@@ -957,6 +957,7 @@
   info.selection_range.set_start(info.selection_range.start() -
                                  text_range.start());
   info.selection_range.set_end(info.selection_range.end() - text_range.start());
+  info.offset = text_range.start();
   return info;
 }
 
diff --git a/ui/base/ime/ash/mock_ime_input_context_handler.cc b/ui/base/ime/ash/mock_ime_input_context_handler.cc
index 8ba4f64..ba356e0 100644
--- a/ui/base/ime/ash/mock_ime_input_context_handler.cc
+++ b/ui/base/ime/ash/mock_ime_input_context_handler.cc
@@ -134,6 +134,7 @@
 SurroundingTextInfo MockIMEInputContextHandler::GetSurroundingTextInfo() {
   SurroundingTextInfo info;
   info.selection_range = cursor_range_;
+  info.offset = 0;
   return info;
 }
 
diff --git a/ui/base/ime/ash/text_input_target.h b/ui/base/ime/ash/text_input_target.h
index adcf1b20..fc8b048 100644
--- a/ui/base/ime/ash/text_input_target.h
+++ b/ui/base/ime/ash/text_input_target.h
@@ -20,7 +20,12 @@
 
 struct SurroundingTextInfo {
   std::u16string surrounding_text;
+
+  // This is relative to the beginning of |surrounding_text|.
   gfx::Range selection_range;
+
+  // Offset of the surrounding_text in the field in UTF-16.
+  size_t offset;
 };
 
 // An interface representing an input target that supports text editing via a
diff --git a/ui/base/test/skia_gold_pixel_diff.cc b/ui/base/test/skia_gold_pixel_diff.cc
index b9bd3139..86edbc0 100644
--- a/ui/base/test/skia_gold_pixel_diff.cc
+++ b/ui/base/test/skia_gold_pixel_diff.cc
@@ -145,6 +145,14 @@
       return "system";
     case TestEnvironmentKey::kProcessor:
       return "processor";
+    case TestEnvironmentKey::kSystemVersion:
+      return "system_version";
+    case TestEnvironmentKey::kGpuDriverVendor:
+      return "driver_vendor";
+    case TestEnvironmentKey::kGpuDriverVersion:
+      return "driver_version";
+    case TestEnvironmentKey::kGlRenderer:
+      return "gl_renderer";
   }
 
   NOTREACHED_NORETURN();
diff --git a/ui/base/test/skia_gold_pixel_diff.h b/ui/base/test/skia_gold_pixel_diff.h
index 4b445aae..a92bdc5 100644
--- a/ui/base/test/skia_gold_pixel_diff.h
+++ b/ui/base/test/skia_gold_pixel_diff.h
@@ -30,7 +30,21 @@
   // The processor architecture.
   kProcessor,
 
-  // Begin subtype-provided keys.
+  // Begin subtype-provided keys.\
+
+  // The version of the operating system.
+  // On Windows, this is the release ID string and is used to track the DWM
+  // version.
+  kSystemVersion,
+
+  // The vendor name of the GPU used for ths test.
+  kGpuDriverVendor,
+
+  // The GPU driver version.
+  kGpuDriverVersion,
+
+  // The GL_RENDERER string returned from the GLContext.
+  kGlRenderer,
 };
 
 using TestEnvironmentMap = base::flat_map<TestEnvironmentKey, std::string>;
diff --git a/ui/events/gestures/gesture_recognizer_impl.h b/ui/events/gestures/gesture_recognizer_impl.h
index 71245062..e20ec8b 100644
--- a/ui/events/gestures/gesture_recognizer_impl.h
+++ b/ui/events/gestures/gesture_recognizer_impl.h
@@ -11,6 +11,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/events_export.h"
 #include "ui/events/gestures/gesture_provider_aura.h"
diff --git a/ui/file_manager/base/gn/uma_enums.cc.jinja b/ui/file_manager/base/gn/uma_enums.cc.jinja
new file mode 100644
index 0000000..de0c417
--- /dev/null
+++ b/ui/file_manager/base/gn/uma_enums.cc.jinja
@@ -0,0 +1,30 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/file_manager/uma_enums.gen.h"
+
+#include <algorithm>
+
+#include "base/strings/string_util.h"
+
+// File generated by //ui/file_manager/base/gn/uma_enums_generate.py.
+namespace file_manager::file_tasks {
+
+ViewFileType GetViewFileType(const base::FilePath& path) {
+  static constexpr auto kViewFileTypes = {
+{% for e in enums %}
+      "{{e.lower()}}",
+{% endfor %}
+  };
+
+  auto* const* it = std::find(kViewFileTypes.begin(), kViewFileTypes.end(),
+                              base::ToLowerASCII(path.FinalExtension()));
+  if (it != kViewFileTypes.end()) {
+    return static_cast<ViewFileType>(std::distance(kViewFileTypes.begin(), it));
+  }
+  return ViewFileType::kOther;
+}
+
+}  // namespace file_manager::file_tasks
+
diff --git a/ui/file_manager/base/gn/uma_enums.h.jinja b/ui/file_manager/base/gn/uma_enums.h.jinja
new file mode 100644
index 0000000..7207fe88
--- /dev/null
+++ b/ui/file_manager/base/gn/uma_enums.h.jinja
@@ -0,0 +1,27 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_FILE_MANAGER_UMA_ENUMS_GEN_H_
+#define CHROME_BROWSER_ASH_FILE_MANAGER_UMA_ENUMS_GEN_H_
+
+#include "base/files/file_path.h"
+
+// File generated by //ui/file_manager/base/gn/uma_enums_generate.py.
+namespace file_manager::file_tasks {
+
+/**
+ * List of file extensions to record in UMA, from enums.xml ViewFileType.
+ */
+enum class ViewFileType {
+{% for e in enums %}
+  k{{e.strip('.').title().replace(' ', '')}},
+{% endfor %}
+  kMaxValue = k{{enums[-1].strip('.').title().replace(' ', '')}},
+};
+
+ViewFileType GetViewFileType(const base::FilePath& path);
+
+}  // namespace file_manager::file_tasks
+
+#endif  // CHROME_BROWSER_ASH_FILE_MANAGER_UMA_ENUMS_GEN_H_
diff --git a/ui/file_manager/base/gn/uma_enums.ts.jinja b/ui/file_manager/base/gn/uma_enums.ts.jinja
new file mode 100644
index 0000000..efb0ed0
--- /dev/null
+++ b/ui/file_manager/base/gn/uma_enums.ts.jinja
@@ -0,0 +1,14 @@
+// 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.
+
+// File generated by //ui/file_manager/base/gn/uma_enums_generate.py.
+
+/**
+ * List of file extensions to record in UMA, from enums.xml ViewFileType.
+ */
+export const UMA_INDEX_KNOWN_EXTENSIONS = Object.freeze([
+{% for e in enums %}
+  '{{e.lower()}}',
+{% endfor %}
+]);
diff --git a/ui/file_manager/base/gn/uma_enums_generate.py b/ui/file_manager/base/gn/uma_enums_generate.py
new file mode 100755
index 0000000..b2b1999
--- /dev/null
+++ b/ui/file_manager/base/gn/uma_enums_generate.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""
+This file generates UMA enums to be used by JS and C++ from enums.xml.
+"""
+
+import os
+import sys
+import xml.etree.ElementTree as ET
+
+# Import jinja2 from //third_party/jinja2
+current_dir = os.path.dirname(__file__)
+src_dir = os.path.join(current_dir, '../../../..')
+sys.path.insert(1, os.path.join(src_dir, 'third_party'))
+import jinja2
+
+root = ET.parse(os.path.join(src_dir, 'tools/metrics/histograms/enums.xml'))
+e = root.find('.//enum[@name="ViewFileType"]')
+enums = [x.attrib['label'] for x in e]
+
+env = jinja2.Environment(loader=jinja2.FileSystemLoader(current_dir),
+                         lstrip_blocks=True,
+                         trim_blocks=True)
+
+
+def render(template, result_file):
+    template = env.get_template(template)
+    with open(os.path.join(src_dir, result_file), 'w') as f:
+        f.write(template.render({'enums': enums}))
+
+
+render('uma_enums.ts.jinja',
+       'ui/file_manager/file_manager/foreground/js/uma_enums.gen.ts')
+render('uma_enums.h.jinja', 'chrome/browser/ash/file_manager/uma_enums.gen.h')
+render('uma_enums.cc.jinja',
+       'chrome/browser/ash/file_manager/uma_enums.gen.cc')
+
+print(
+    'Done. Format using: git cl format && git cl format --js ui/file_manager')
diff --git a/ui/file_manager/file_manager/BUILD.gn b/ui/file_manager/file_manager/BUILD.gn
index fae331b..21c64c8 100644
--- a/ui/file_manager/file_manager/BUILD.gn
+++ b/ui/file_manager/file_manager/BUILD.gn
@@ -85,6 +85,7 @@
     "foreground/images/files/ui/ms365.svg",
     "foreground/images/files/ui/nudge_star_icon.svg",
     "foreground/images/files/ui/offline.svg",
+    "foreground/images/files/ui/odfs_reauthentication_required.svg",
     "foreground/images/files/ui/photos_logo.svg",
     "foreground/images/files/ui/refresh.svg",
     "foreground/images/files/ui/restore.svg",
diff --git a/ui/file_manager/file_manager/background/js/drive_sync_handler.js b/ui/file_manager/file_manager/background/js/drive_sync_handler.js
index feeab9c..2a6063a 100644
--- a/ui/file_manager/file_manager/background/js/drive_sync_handler.js
+++ b/ui/file_manager/file_manager/background/js/drive_sync_handler.js
@@ -346,14 +346,11 @@
         ],
         completedValues);
 
-    // The UX spec determines completed sync status should be displayed for
-    // 300ms before transitioning to other statuses. Let's pull filtered states
-    // that have completed in 300ms from now.
+    // Hold "completed" state for 300ms to give users a chance to see it.
     await new Promise(r => setTimeout(r, 300));
 
     const {allEntries} = getStore().getState();
-    // Note: Volume Entries have to be filtered out because they are not
-    // accepted by the `MetadataModel.get()` method.
+    // Unwrap entries so they are accepted by the `MetadataModel.get()` method.
     const completedEntries = completedUrls.map(url => allEntries[url]?.entry)
                                  .filter(Boolean)
                                  .map(util.unwrapEntry);
diff --git a/ui/file_manager/file_manager/containers/nudge_container.ts b/ui/file_manager/file_manager/containers/nudge_container.ts
index 3631fa8..e5663c9 100644
--- a/ui/file_manager/file_manager/containers/nudge_container.ts
+++ b/ui/file_manager/file_manager/containers/nudge_container.ts
@@ -201,7 +201,14 @@
       // Self dismissable nudge only dismisses if the user clicks on the nudge.
       this.nudge_.addEventListener(
           'pointerdown', () => this.closeNudge(this.currentNudgeType_), config);
-      // TODO(lucmult): Dismiss via keyboard.
+      const dismissOnKeyDown = info.dismissOnKeyDown;
+      if (dismissOnKeyDown) {
+        document.addEventListener('keydown', (event: KeyboardEvent) => {
+          if (dismissOnKeyDown(anchor, event)) {
+            this.closeNudge(this.currentNudgeType_);
+          }
+        }, config);
+      }
     } else {
       // Otherwise the nudge dismisses when user clicks anywhere in the app.
       document.addEventListener('keydown', e => this.handleKeyDown_(e), config);
@@ -353,6 +360,33 @@
   // nudge. Otherwise the nudge is dismissed when clicking anywhere in
   // the app/document.
   selfDismiss?: boolean;
+
+  // For selfDismiss nudge the nudge and its anchor might not get keyboard focus
+  // to be able to dismiss via keyboard.
+  // Implement this callback that receives the keydown from document and should
+  // return true if the nudge should be dismissed.
+  dismissOnKeyDown?:
+      (anchor: HTMLElement|null, event: KeyboardEvent) => boolean;
+}
+
+/**
+ * Dismisses the nudge when the tree-item that anchors the nudge is selected.
+ *
+ * NOTE: It relies on the nudge anchor being in the icon, to traverse 2 parents
+ * up to the tree-item.
+ */
+function treeDismissOnKeyDownOnTreeItem(
+    anchor: HTMLElement|null, event: KeyboardEvent) {
+  const dismissKeys = new Set(['Enter', 'Space']);
+  if (!dismissKeys.has(event.key)) {
+    return false;
+  }
+
+  // When the anchor (tree item) is selected we dismiss.
+  if (anchor?.parentElement?.parentElement?.hasAttribute('selected')) {
+    return true;
+  }
+  return false;
 }
 
 /**
@@ -396,6 +430,7 @@
     direction: NudgeDirection.TRAILING_DOWNWARD,
     expiryDate: new Date(2999, 1, 1),
     selfDismiss: true,
+    dismissOnKeyDown: treeDismissOnKeyDownOnTreeItem,
   },
   [NudgeType['ONE_DRIVE_MOVED_FILE_NUDGE']]: {
     anchor: () => {
@@ -410,6 +445,7 @@
     // Expire after 4 releases (expires when M120 hits Stable).
     expiryDate: new Date(2023, 12, 5),
     selfDismiss: true,
+    dismissOnKeyDown: treeDismissOnKeyDownOnTreeItem,
   },
   [NudgeType['DRIVE_MOVED_FILE_NUDGE']]: {
     anchor: () => {
@@ -424,6 +460,7 @@
     // Expire after 4 releases (expires when M120 hits Stable).
     expiryDate: new Date(2023, 12, 5),
     selfDismiss: true,
+    dismissOnKeyDown: treeDismissOnKeyDownOnTreeItem,
   },
   [NudgeType['SEARCH_V2_EDUCATION_NUDGE']]: {
     anchor: () => document.querySelector<HTMLSpanElement>('#search-wrapper'),
diff --git a/ui/file_manager/file_manager/containers/nudge_container_unittest.ts b/ui/file_manager/file_manager/containers/nudge_container_unittest.ts
index 3a010e8..25f84f2 100644
--- a/ui/file_manager/file_manager/containers/nudge_container_unittest.ts
+++ b/ui/file_manager/file_manager/containers/nudge_container_unittest.ts
@@ -225,8 +225,7 @@
 }
 
 /**
- * Tests the nudge is dismissed by the dismiss button, when it has a
- * dismissText.
+ * Tests the nudge is dismissed by clicking on the nudge.
  */
 export async function testNudgeDismissButton(done: () => void) {
   nudgeInfo[NudgeType.TEST_NUDGE].selfDismiss = true;
@@ -243,3 +242,36 @@
 
   done();
 }
+
+/**
+ * Tests the nudge using the dismissOnKeyDown().
+ */
+export async function testNudgeDismissKeyDown(done: () => void) {
+  nudgeInfo[NudgeType.TEST_NUDGE].selfDismiss = true;
+  nudgeInfo[NudgeType.TEST_NUDGE].dismissOnKeyDown =
+      (_, event: KeyboardEvent) => {
+        // In tests we can send the keydown directly to the nudge.
+        if (event.target === nudgeElement) {
+          return true;
+        }
+        return false;
+      };
+  await createAndShowTestNudge();
+
+  // Send a keydown somewhere else, should not dismiss.
+  document.body.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true}));
+  assertFalse(
+      await nudgeContainer!.checkSeen(NudgeType.TEST_NUDGE),
+      `nudge shouldn't be dismissed by keydown on <body>`);
+
+  // Send keydown to the nudge.
+  nudgeElement!.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true}));
+
+  // Reposition to hidden.
+  await waitUntilRepositionsUninitialised();
+  assertTrue(
+      await nudgeContainer!.checkSeen(NudgeType.TEST_NUDGE),
+      'check nudge has been seen');
+
+  done();
+}
diff --git a/ui/file_manager/file_manager/containers/search_container.ts b/ui/file_manager/file_manager/containers/search_container.ts
index 6b3f2ab..a160769 100644
--- a/ui/file_manager/file_manager/containers/search_container.ts
+++ b/ui/file_manager/file_manager/containers/search_container.ts
@@ -633,13 +633,13 @@
     // in the OPENING state, without ever getting to OPEN state.
     if (this.inputState_ === SearchInputState.CLOSED) {
       this.inputState_ = SearchInputState.OPENING;
-      this.inputElement_.disabled = false;
-      this.inputElement_.tabIndex = 0;
-      this.inputElement_.focus();
       this.inputElement_.addEventListener('transitionend', () => {
         this.inputState_ = SearchInputState.OPEN;
         this.searchWrapper_.removeAttribute('collapsed');
       }, {once: true, passive: true, capture: true});
+      this.inputElement_.disabled = false;
+      this.inputElement_.tabIndex = 0;
+      this.inputElement_.focus();
       this.searchWrapper_.classList.add('has-cursor', 'has-text');
       this.searchBox_.classList.add('has-cursor', 'has-text');
       this.searchButton_.tabIndex = -1;
@@ -656,17 +656,17 @@
     // Do not initiate close transition if we are not open. This would leave us
     // in the CLOSING state, without ever getting to CLOSED state.
     if (this.inputState_ === SearchInputState.OPEN) {
-      this.hideOptionsElement_();
-      this.hidePathDisplayElement_();
-      this.store_.dispatch(clearSearch());
       this.inputState_ = SearchInputState.CLOSING;
-      this.inputElement_.tabIndex = -1;
-      this.inputElement_.disabled = true;
-      this.inputElement_.blur();
       this.inputElement_.addEventListener('transitionend', () => {
         this.inputState_ = SearchInputState.CLOSED;
         this.searchWrapper_.setAttribute('collapsed', '');
       }, {once: true, passive: true, capture: true});
+      this.hideOptionsElement_();
+      this.hidePathDisplayElement_();
+      this.store_.dispatch(clearSearch());
+      this.inputElement_.tabIndex = -1;
+      this.inputElement_.disabled = true;
+      this.inputElement_.blur();
       this.inputElement_.value = '';
       this.searchWrapper_.classList.remove('has-cursor', 'has-text');
       this.searchBox_.classList.remove('has-cursor', 'has-text');
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index 320dc3c..387de21 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -807,7 +807,7 @@
 
 /* Search button */
 body.check-select-v1 #search-button {
-  display: none;
+  visibility: hidden;
 }
 
 /* Search box */
@@ -828,10 +828,6 @@
   caret-color: var(--cr-input-color);
 }
 
-body.check-select-v1 #search-box {
-  display: none;
-}
-
 .dialog-header.files-ng #search-box.has-cursor,
 .dialog-header.files-ng #search-box.has-text,
 .dialog-header.files-ng #search-box.hide-pending {
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager_gm3.css b/ui/file_manager/file_manager/foreground/css/file_manager_gm3.css
index 5a25079..f5cfa7ed 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager_gm3.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager_gm3.css
@@ -648,7 +648,7 @@
 
 /* Search button */
 body.check-select-v1 #search-button {
-  display: none;
+  visibility: hidden;
 }
 
 /* Search box */
@@ -663,10 +663,6 @@
   margin-inline-end: 16px;
 }
 
-body.check-select-v1 #search-box {
-  display: none;
-}
-
 .dialog-header.files-ng #search-box.has-cursor,
 .dialog-header.files-ng #search-box.has-text,
 .dialog-header.files-ng #search-box.hide-pending {
diff --git a/ui/file_manager/file_manager/foreground/images/files/ui/odfs_reauthentication_required.svg b/ui/file_manager/file_manager/foreground/images/files/ui/odfs_reauthentication_required.svg
new file mode 100644
index 0000000..0221726
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/images/files/ui/odfs_reauthentication_required.svg
@@ -0,0 +1,14 @@
+<svg id="odfs_reauthentication_required" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 200 100" xml:space="preserve" fill="none">
+<path d="M123.7 57.3004C123.7 49.8004 119.4 43.2004 113.2 39.9004C104 41.6004 97 49.6004 97 59.2004V92.7004H123.6L123.7 57.3004Z" fill="var(--cros-sys-illo-color1)" />
+<path d="M105.9 76.6996C107.5 74.6996 107.2 71.6996 105.2 70.0996L105.1 69.9997L96.1001 62.9997C94.0001 61.3997 90.9001 61.7997 89.3001 63.8997C87.7001 65.9997 88.0001 68.9996 90.1001 70.6996C90.1001 70.6996 90.1001 70.6997 90.2001 70.7997L99.2001 77.6996C101.2 79.2996 104.2 78.9997 105.8 76.9997C105.8 76.7996 105.8 76.7996 105.9 76.6996Z" fill="var(--cros-sys-illo-color1-2)" />
+<path d="M84.5 75.6002V57.3002C84.5 46.5002 93.3 37.7002 104.1 37.7002C114.9 37.7002 123.7 46.5002 123.7 57.3002V92.7002H84.5V75.6002Z" stroke="var(--cros-sys-illo-color1)" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" />
+<path d="M115.7 66.0996C115.3 66.0996 114.8 66.1996 114.5 66.4996L108.9 72.4996C108.6 72.7996 108.4 73.2996 108.6 73.7996L110.8 81.5996C111 81.9996 111.4 82.3996 111.8 82.5996L119.8 84.4996C120.3 84.6996 120.8 84.4996 121 84.0996L123.2 81.7996V67.8996L115.7 66.0996Z" fill="var(--cros-sys-illo-secondary)" />
+<path d="M59.5 42.4004L56.9001 44.7004L59.2001 47.3004L55.3 50.7004L57.6 53.3004L61.5 49.9004L66.2 55.4004C64.8 57.6004 65.0001 60.5004 66.8001 62.6004C69.0001 65.1004 72.8001 65.4004 75.3001 63.1004C77.8001 60.9004 78.1001 57.1004 75.8001 54.6004C74.0001 52.5004 71.1 52.0004 68.7 53.1004L59.5 42.4004ZM69.6 56.7004C70.7 55.8004 72.3001 55.9004 73.3001 56.9004C74.2001 58.0004 74.1 59.6004 73.1 60.6004C72 61.5004 70.4001 61.4004 69.4001 60.4004C68.4001 59.3004 68.5 57.6004 69.6 56.7004Z" fill="var(--cros-sys-illo-color3)" />
+<path d="M135.6 44.2996C136.76 44.2996 137.7 43.3594 137.7 42.1996C137.7 41.0398 136.76 40.0996 135.6 40.0996C134.44 40.0996 133.5 41.0398 133.5 42.1996C133.5 43.3594 134.44 44.2996 135.6 44.2996Z" fill="var(--cros-sys-illo-color5)" />
+<path d="M157.6 47.2004L153 39.8004C152.4 38.9004 152.7 37.6004 153.6 37.0004L161.1 32.3004C162 31.7004 163.3 32.0004 163.9 32.9004L168.5 40.3004C169.1 41.2004 168.8 42.5004 167.9 43.1004L160.4 47.8004C159.4 48.5004 158.2 48.2004 157.6 47.2004Z" fill="var(--cros-sys-illo-color3)" />
+<path d="M141.7 26.4996C144.572 26.4996 146.9 24.1715 146.9 21.2996C146.9 18.4277 144.572 16.0996 141.7 16.0996C138.828 16.0996 136.5 18.4277 136.5 21.2996C136.5 24.1715 138.828 26.4996 141.7 26.4996Z" fill="var(--cros-sys-illo-color1)" />
+<path d="M27.4 23.8008C27.1 23.8008 26.7 23.8008 26.4 23.9008C24.8 24.2008 23.4 25.1008 22.5 26.6008C21.7 27.9008 21.5 29.6008 22 31.2008C22.3 32.2008 22.8 33.1008 23.6 33.7008C27.5 32.7008 30.6 29.6008 31.8 25.8008C31.6 25.6008 31.4 25.4008 31.2 25.3008C30.1 24.3008 28.8 23.8008 27.4 23.8008Z" fill="var(--cros-sys-illo-color2)" />
+<path d="M52.4999 36.4C51.8999 33.7 50.1999 31.4 47.8999 30C46.7999 29.4 45.5999 29 44.0999 28.8C42.8999 28.7 41.6999 28.6 40.5999 28.6H39.5999C37.7999 28.6 35.9999 27.8 34.6999 26.5C34.4999 26.3 34.2999 26 34.0999 25.8C33.4999 25.2 32.8999 24.6 32.2999 24C32.1999 24.6 32.0999 25.1 31.8999 25.7C32.2999 26.1 32.5999 26.4 32.9999 26.8C33.1999 27 33.3999 27.3 33.6999 27.5C35.2999 29.1 37.3999 30 39.5999 30H40.5999C41.6999 30 42.8999 30 43.9999 30.1C45.2999 30.3 46.2999 30.6 47.1999 31.1C49.1999 32.3 50.5999 34.3 51.0999 36.6C51.5999 38.9 51.1999 41.3 49.7999 43.2C47.4999 46.6 42.8999 47.9 39.1999 46.1C37.8999 45.5 36.8999 44.6 36.0999 43.5C35.4999 42.7 34.9999 41.8 34.4999 40.8C34.2999 40.4 34.0999 40 33.7999 39.6C32.8999 37.9 31.3999 36.6 29.4999 35.9C28.7999 35.6 28.0999 35.4 27.3999 35.2C26.4999 34.9 25.5999 34.7 24.7999 34.3C24.3999 34.1 23.9999 33.8 23.5999 33.5C22.9999 33.7 22.3999 33.8 21.7999 33.8C22.3999 34.6 23.1999 35.2 23.9999 35.6C24.8999 36.1 25.8999 36.3 26.7999 36.6C27.4999 36.8 28.0999 37 28.7999 37.2C30.2999 37.8 31.5999 38.9 32.2999 40.2C32.4999 40.6 32.6999 41 32.8999 41.4C33.3999 42.4 33.8999 43.3 34.5999 44.3C35.5999 45.6 36.7999 46.7 38.2999 47.4C39.6999 48 41.0999 48.4 42.4999 48.4C45.6999 48.4 48.8999 46.8 50.7999 44.1C52.4999 41.9 53.0999 39.1 52.4999 36.4Z" fill="var(--cros-sys-illo-color1-2)" />
+<path d="M21.2 25.8004C22.3 24.0004 24 22.8004 26.1 22.4004C28.2 22.0004 30.4 22.6004 32.1 24.0004C32.2 23.4004 32.2 22.8004 32.2 22.2004C32.2 15.7004 26.9 10.4004 20.4 10.4004C13.9 10.4004 8.59998 15.7004 8.59998 22.2004C8.69998 28.8004 14 34.0004 20.5 34.0004C20.9 34.0004 21.4 34.0004 21.8 33.9004C21.2 33.2004 20.8 32.4004 20.6 31.5004C20 29.6004 20.2 27.5004 21.2 25.8004Z" fill="var(--cros-sys-illo-color2)" />
+<path d="M22 31.1004C21.5 29.5004 21.7 27.9004 22.5 26.5004C23.4 25.1004 24.7 24.1004 26.4 23.8004C26.7 23.7004 27.1 23.7004 27.4 23.7004C28.8 23.7004 30.1 24.2004 31.2 25.1004C31.4 25.3004 31.6 25.5004 31.8 25.6004C32 25.1004 32.1 24.5004 32.2 23.9004C30.5 22.5004 28.3 21.9004 26.2 22.3004C24.1 22.7004 22.4 23.9004 21.3 25.7004C20.3 27.4004 20 29.5004 20.6 31.5004C20.9 32.4004 21.3 33.2004 21.8 33.9004C22.4 33.8004 23 33.7004 23.6 33.6004C22.8 33.0004 22.3 32.1004 22 31.1004Z" fill="var(--cros-sys-illo-base)" />
+</svg>
\ No newline at end of file
diff --git a/ui/file_manager/file_manager/foreground/js/empty_folder_controller.js b/ui/file_manager/file_manager/foreground/js/empty_folder_controller.js
index 712d807..b6a7e3e 100644
--- a/ui/file_manager/file_manager/foreground/js/empty_folder_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/empty_folder_controller.js
@@ -38,6 +38,16 @@
     'foreground/images/files/ui/empty_trash_folder.svg#empty_trash_folder';
 
 /**
+ * The reauthentication required image for ODFS. There are no files when
+ * reauthentication is required (scan fails).
+ * TODO(b/279109210): fix light/dark colours.
+ * @type {string}
+ * @const
+ */
+const ODFS_REAUTHENTICATION_REQUIRED = 'foreground/images/files/ui/' +
+    'odfs_reauthentication_required.svg#odfs_reauthentication_required';
+
+/**
  * Empty folder controller which controls the empty folder element inside
  * the file list container.
  */
@@ -82,7 +92,7 @@
     this.directoryModel_.addEventListener(
         'scan-started', this.onScanStarted_.bind(this));
     this.directoryModel_.addEventListener(
-        'scan-failed', this.onScanFinished_.bind(this));
+        'scan-failed', this.onScanFailed_.bind(this));
     this.directoryModel_.addEventListener(
         'scan-cancelled', this.onScanFinished_.bind(this));
     this.directoryModel_.addEventListener(
@@ -101,6 +111,24 @@
   }
 
   /**
+   * Handles scan fail.
+   * @private
+   */
+  onScanFailed_(event) {
+    this.isScanning_ = false;
+    let odfsAndReauthenticationRequired = false;
+    // Check if ODFS is the volume and reauthentication is required.
+    // NO_MODIFICATION_ALLOWED_ERR is equivalent to the ACCESS_DENIED error
+    // thrown by ODFS.
+    const currentVolumeInfo = this.directoryModel_.getCurrentVolumeInfo();
+    if (util.isOneDrive(currentVolumeInfo) &&
+        event.error.name == util.FileError.NO_MODIFICATION_ALLOWED_ERR) {
+      odfsAndReauthenticationRequired = true;
+    }
+    this.updateUI_(odfsAndReauthenticationRequired);
+  }
+
+  /**
    * Handles scan finish.
    * @private
    */
@@ -133,10 +161,12 @@
   }
 
   /**
-   * Updates visibility of empty folder UI.
+   * Updates visibility of empty folder UI. `odfsAndReauthenticationRequired` is
+   * true when the volume is ODFS and reauthentication is required.
+   * @param {boolean} odfsAndReauthenticationRequired
    * @private
    */
-  updateUI_() {
+  updateUI_(odfsAndReauthenticationRequired = false) {
     const currentRootType = this.directoryModel_.getCurrentRootType();
 
     let svgRef = null;
@@ -144,6 +174,8 @@
       svgRef = RECENTS_EMPTY_FOLDER;
     } else if (currentRootType === VolumeManagerCommon.RootType.TRASH) {
       svgRef = TRASH_EMPTY_FOLDER;
+    } else if (odfsAndReauthenticationRequired) {
+      svgRef = ODFS_REAUTHENTICATION_REQUIRED;
     } else {
       if (util.isSearchV2Enabled()) {
         const {search} = getStore().getState();
@@ -172,6 +204,15 @@
       return;
     }
 
+    if (svgRef == ODFS_REAUTHENTICATION_REQUIRED) {
+      // TODO(b/254586358): i18n these strings.
+      this.showMessage_(
+          'You\'ve been logged out',
+          'Sign in to your OneDrive account to open Office files' +
+              '\nstored on your Chromebook or go to Settings');
+      return;
+    }
+
     if (svgRef === SEARCH_EMPTY_RESULTS) {
       this.showMessage_(
           str('SEARCH_NO_MATCHING_RESULTS_TITLE'),
diff --git a/ui/file_manager/file_manager/foreground/js/empty_folder_controller_unittest.js b/ui/file_manager/file_manager/foreground/js/empty_folder_controller_unittest.js
index 39bf2e82..2febeee 100644
--- a/ui/file_manager/file_manager/foreground/js/empty_folder_controller_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/empty_folder_controller_unittest.js
@@ -10,6 +10,8 @@
 import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
 import {FakeEntry} from '../../externs/files_app_entry_interfaces.js';
 import {PropStatus} from '../../externs/ts/state.js';
+import {VolumeInfo} from '../../externs/volume_info.js';
+import {constants} from '../../foreground/js/constants.js';
 import {clearSearch, updateSearch} from '../../state/actions/search.js';
 import {getEmptyState, getStore} from '../../state/store.js';
 
@@ -153,6 +155,24 @@
 }
 
 /**
+ * Tests that no files message will be hidden if the volume is ODFS
+ * but the scan finished with no error.
+ * @suppress {accessControls} access private method in test.
+ */
+export function testHiddenForODFS() {
+  // Set ODFS as the volume.
+  directoryModel.getCurrentVolumeInfo = function() {
+    return /** @type {!VolumeInfo} */ ({
+      providerId: constants.ODFS_EXTENSION_ID,
+    });
+  };
+
+  emptyFolderController.onScanFinished_();
+  assertTrue(element.hidden);
+  assertEquals('', emptyFolderController.label_.innerText);
+}
+
+/**
  * Tests that the empty state image shows up when root type is Trash.
  * @suppress {accessControls} access private method in test.
  */
@@ -165,6 +185,31 @@
 }
 
 /**
+ * Tests that the reauthentication required image shows up when the volume is
+ * ODFS and the scan failed from a NO_MODIFICATION_ALLOWED_ERR.
+ * @suppress {accessControls} access private method in test.
+ */
+export function testShownForODFS() {
+  // Set ODFS as the volume.
+  directoryModel.getCurrentVolumeInfo = function() {
+    return /** @type {!VolumeInfo} */ ({
+      providerId: constants.ODFS_EXTENSION_ID,
+    });
+  };
+
+  // Pass a NO_MODIFICATION_ALLOWED_ERR error (implies reauthentication
+  // required).
+  const event = new Event('scan-failed');
+  event.error = {name: util.FileError.NO_MODIFICATION_ALLOWED_ERR};
+  emptyFolderController.onScanFailed_(event);
+
+  assertFalse(element.hidden);
+  const text = emptyFolderController.label_.innerText;
+  // TODO(b/254586358): use the i18n version of this string.
+  assertTrue(text.includes('You\'ve been logged out'));
+}
+
+/**
  * Tests that the empty state image shows up when search is active.
  * @suppress {accessControls} access private method in test.
  */
diff --git a/ui/file_manager/file_manager/foreground/js/file_tasks.ts b/ui/file_manager/file_manager/foreground/js/file_tasks.ts
index cae9e5d..280ecc9e 100644
--- a/ui/file_manager/file_manager/foreground/js/file_tasks.ts
+++ b/ui/file_manager/file_manager/foreground/js/file_tasks.ts
@@ -34,6 +34,7 @@
 import {DefaultTaskDialog} from './ui/default_task_dialog.js';
 import {FileManagerUI} from './ui/file_manager_ui.js';
 import {FilesConfirmDialog} from './ui/files_confirm_dialog.js';
+import {UMA_INDEX_KNOWN_EXTENSIONS} from './uma_enums.gen.js';
 
 /**
  * Office file handlers UMA values (must be consistent with OfficeFileHandler in
@@ -843,43 +844,6 @@
 } as const;
 type TypeTaskPickerType = typeof TaskPickerType[keyof typeof TaskPickerType];
 
-/**
- * List of file extensions to record in UMA.
- *
- * Note: since the data is recorded by list index, new items should be added
- * to the end of this list.
- *
- * The list must also match the FileBrowser ViewFileType entry in enums.xml.
- */
-export const UMA_INDEX_KNOWN_EXTENSIONS = Object.freeze([
-  'other',     '.3ga',         '.3gp',
-  '.aac',      '.alac',        '.asf',
-  '.avi',      '.bmp',         '.csv',
-  '.doc',      '.docx',        '.flac',
-  '.gif',      '.jpeg',        '.jpg',
-  '.log',      '.m3u',         '.m3u8',
-  '.m4a',      '.m4v',         '.mid',
-  '.mkv',      '.mov',         '.mp3',
-  '.mp4',      '.mpg',         '.odf',
-  '.odp',      '.ods',         '.odt',
-  '.oga',      '.ogg',         '.ogv',
-  '.pdf',      '.png',         '.ppt',
-  '.pptx',     '.ra',          '.ram',
-  '.rar',      '.rm',          '.rtf',
-  '.wav',      '.webm',        '.webp',
-  '.wma',      '.wmv',         '.xls',
-  '.xlsx',     '.crdownload',  '.crx',
-  '.dmg',      '.exe',         '.html',
-  '.htm',      '.jar',         '.ps',
-  '.torrent',  '.txt',         '.zip',
-  'directory', 'no extension', 'unknown extension',
-  '.mhtml',    '.gdoc',        '.gsheet',
-  '.gslides',  '.arw',         '.cr2',
-  '.dng',      '.nef',         '.nrw',
-  '.orf',      '.raf',         '.rw2',
-  '.tini',
-]);
-
 /** Office file extensions. */
 const OFFICE_EXTENSIONS =
     new Set(['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']);
diff --git a/ui/file_manager/file_manager/foreground/js/quick_view_uma.js b/ui/file_manager/file_manager/foreground/js/quick_view_uma.js
index 2e76644..befee9c 100644
--- a/ui/file_manager/file_manager/foreground/js/quick_view_uma.js
+++ b/ui/file_manager/file_manager/foreground/js/quick_view_uma.js
@@ -10,7 +10,7 @@
 import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
 import {VolumeManager} from '../../externs/volume_manager.js';
 
-import {UMA_INDEX_KNOWN_EXTENSIONS} from './file_tasks.js';
+import {UMA_INDEX_KNOWN_EXTENSIONS} from './uma_enums.gen.js';
 
 /**
  * UMA exporter for Quick View.
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
index 7cbd0a7..c09f6833 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
@@ -591,9 +591,7 @@
           let progress = externalProps.progress ?? 0;
           const {syncCompletedTime} = externalProps;
 
-          // The UX spec determines completed sync status should be displayed
-          // for 300ms before transitioning to other statuses. Let's hold the
-          // "completed" state for at least 300ms.
+          // Hold "completed" state for 300ms to give users a chance to see it.
           if (syncCompletedTime && Date.now() - syncCompletedTime < 300) {
             syncStatus = chrome.fileManagerPrivate.SyncStatus.COMPLETED;
             progress = 1.0;
diff --git a/ui/file_manager/file_manager/foreground/js/uma_enums.gen.ts b/ui/file_manager/file_manager/foreground/js/uma_enums.gen.ts
new file mode 100644
index 0000000..079a9583
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/js/uma_enums.gen.ts
@@ -0,0 +1,37 @@
+// 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.
+
+// File generated by //ui/file_manager/base/gn/uma_enums_generate.py.
+
+/**
+ * List of file extensions to record in UMA, from enums.xml ViewFileType.
+ */
+export const UMA_INDEX_KNOWN_EXTENSIONS = Object.freeze([
+  'other',     '.3ga',         '.3gp',
+  '.aac',      '.alac',        '.asf',
+  '.avi',      '.bmp',         '.csv',
+  '.doc',      '.docx',        '.flac',
+  '.gif',      '.jpeg',        '.jpg',
+  '.log',      '.m3u',         '.m3u8',
+  '.m4a',      '.m4v',         '.mid',
+  '.mkv',      '.mov',         '.mp3',
+  '.mp4',      '.mpg',         '.odf',
+  '.odp',      '.ods',         '.odt',
+  '.oga',      '.ogg',         '.ogv',
+  '.pdf',      '.png',         '.ppt',
+  '.pptx',     '.ra',          '.ram',
+  '.rar',      '.rm',          '.rtf',
+  '.wav',      '.webm',        '.webp',
+  '.wma',      '.wmv',         '.xls',
+  '.xlsx',     '.crdownload',  '.crx',
+  '.dmg',      '.exe',         '.html',
+  '.htm',      '.jar',         '.ps',
+  '.torrent',  '.txt',         '.zip',
+  'directory', 'no extension', 'unknown extension',
+  '.mhtml',    '.gdoc',        '.gsheet',
+  '.gslides',  '.arw',         '.cr2',
+  '.dng',      '.nef',         '.nrw',
+  '.orf',      '.raf',         '.rw2',
+  '.tini',
+]);
diff --git a/ui/file_manager/file_manager/widgets/xf_bulk_pinning_dialog.ts b/ui/file_manager/file_manager/widgets/xf_bulk_pinning_dialog.ts
index 1d0f2c92..a4379b2fcc 100644
--- a/ui/file_manager/file_manager/widgets/xf_bulk_pinning_dialog.ts
+++ b/ui/file_manager/file_manager/widgets/xf_bulk_pinning_dialog.ts
@@ -314,7 +314,7 @@
       }
 
       a {
-        color: currentcolor;
+        color: var(--cros-sys-primary);
       }
 
       files-spinner {
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index ffdb0f7..47c2069 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -310,6 +310,7 @@
   # Foreground.
   "file_manager/foreground/js/file_tasks.ts",
   "file_manager/foreground/js/task_controller.ts",
+  "file_manager/foreground/js/uma_enums.gen.ts",
 
   # Util
   "file_manager/widgets/xf_tree_util.ts",
diff --git a/ui/file_manager/integration_tests/file_manager/search.js b/ui/file_manager/integration_tests/file_manager/search.js
index 729ef50b4..1fc9b07 100644
--- a/ui/file_manager/integration_tests/file_manager/search.js
+++ b/ui/file_manager/integration_tests/file_manager/search.js
@@ -869,8 +869,8 @@
   // Navigate to Trash and confirm that the search button is now hidden.
   await navigateWithDirectoryTree(appId, '/Trash');
   searchButton = await remoteCall.waitForElementStyles(
-      appId, ['#search-button'], ['display']);
-  chrome.test.assertTrue(searchButton.styles['display'] === 'none');
+      appId, ['#search-button'], ['visibility']);
+  chrome.test.assertTrue(searchButton.styles['visibility'] === 'hidden');
 
   // Try to use keyboard shortcuts Ctrl+F to launch search anyway.
   const ctrlF = ['#file-list', 'f', true, false, false];
@@ -884,11 +884,53 @@
   // Go back to Downloads and confirm that the search button is visible again.
   await navigateWithDirectoryTree(appId, '/My files/Downloads');
   searchButton = await remoteCall.waitForElementStyles(
-      appId, ['#search-button'], ['display']);
-  chrome.test.assertTrue(searchButton.styles['display'] !== 'none');
+      appId, ['#search-button'], ['visibility']);
+  chrome.test.assertTrue(searchButton.styles['visibility'] !== 'hidden');
 
   // Make sure that search still works.
   await remoteCall.typeSearchText(appId, 'hello');
   await remoteCall.waitForFiles(
       appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));
 };
+
+/**
+ * Checks that files in trash do not appear in the search results when trash
+ * is enabled, and appear when it is disabled.
+ */
+testcase.searchTrashedFiles = async () => {
+  const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
+
+  // Search for all files with "hello" in their name.
+  await remoteCall.typeSearchText(appId, 'hello');
+  // Confirm that we can find hello.txt.
+  await remoteCall.waitForFiles(
+      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));
+
+  // Clear and close search.
+  await remoteCall.waitAndClickElement(appId, '#search-box .clear');
+
+  // Select hello.txt.
+  await remoteCall.waitAndClickElement(
+      appId, '#file-list [file-name="hello.txt"]');
+  // Delete hello.txt and wait for it to be moved to trash.
+  await remoteCall.clickTrashButton(appId);
+
+  // Search for all files with "hello" in their name.
+  await remoteCall.typeSearchText(appId, 'hello');
+  // Confirm that we cannot find it.
+  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([]));
+
+  // Disable trash.
+  await sendTestMessage({name: 'setTrashEnabled', enabled: false});
+
+  // Search for all files with "hello" in their name.
+  await remoteCall.typeSearchText(appId, 'hello');
+  // Confirm that we can find it. We also expect trashinfo file to appear.
+  const helloTrashinfo = ENTRIES.hello.cloneWith({
+    nameText: 'hello.txt.trashinfo',
+    typeText: 'TRASHINFO file',
+  });
+  await remoteCall.waitForFiles(
+      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello, helloTrashinfo]),
+      {ignoreLastModifiedTime: true, ignoreFileSize: true});
+};
diff --git a/ui/file_manager/integration_tests/file_manager/trash.js b/ui/file_manager/integration_tests/file_manager/trash.js
index e8e5f96..fc3bd3d 100644
--- a/ui/file_manager/integration_tests/file_manager/trash.js
+++ b/ui/file_manager/integration_tests/file_manager/trash.js
@@ -11,17 +11,6 @@
 import {BASIC_ANDROID_ENTRY_SET, BASIC_DRIVE_ENTRY_SET, BASIC_LOCAL_ENTRY_SET, NESTED_ENTRY_SET} from './test_data.js';
 
 /**
- * Clicks the enabled and visible move to trash button and ensures the delete
- * button is hidden.
- * @param {string} appId
- */
-async function clickTrashButton(appId) {
-  await remoteCall.waitForElement(appId, '#delete-button[hidden]');
-  await remoteCall.waitAndClickElement(
-      appId, '#move-to-trash-button:not([hidden]):not([disabled])');
-}
-
-/**
  * Clicks the enabled and visible delete button and ensures the move to trash
  * button is hidden.
  * @param {string} appId
@@ -99,7 +88,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -148,7 +137,7 @@
   // Delete photos dir (no dialog),
   await remoteCall.waitAndClickElement(
       appId, '#file-list [file-name="photos"]');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
 
   // Wait for photos to be removed, and .Trash to be recreated.
   await remoteCall.waitForElementLost(appId, '#file-list [file-name="photos"]');
@@ -203,7 +192,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -228,7 +217,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -257,7 +246,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -305,7 +294,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -340,7 +329,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -372,7 +361,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -407,7 +396,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -432,7 +421,7 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -462,7 +451,7 @@
   await remoteCall.waitForElement(appId, '#tasks:not([hidden])');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -492,7 +481,7 @@
       appId, DOWNLOADS_FAKE_TASKS[0].descriptor, ['hello.txt']);
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -521,7 +510,7 @@
 
   // Delete item and wait for it to be removed (no dialog).
   await remoteCall.waitUntilSelected(appId, 'hello.txt');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -553,7 +542,7 @@
   // Select the Photos folder and trash the whole thing.
   await remoteCall.waitAndClickElement(
       appId, '#file-list [file-name="photos"]');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, '#file-list [file-name="photos"]');
 
   // Navigate to /Trash and ensure the "photos" folder is shown.
@@ -810,7 +799,7 @@
   // Select hello.txt and make sure a default task is executed when double
   // clicking.
   await remoteCall.waitAndClickElement(appId, fileNameSelector);
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, fileNameSelector);
 
   // Navigate to /Trash and ensure the file is there and has not been deleted,
@@ -852,7 +841,7 @@
   // Select hello.txt and send it to the Trash.
   await remoteCall.waitAndClickElement(
       appId, '#file-list [file-name="hello.txt"]');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -903,7 +892,7 @@
   // Select hello.txt and send it to the Trash.
   await remoteCall.waitAndClickElement(
       appId, '#file-list [file-name="hello.txt"]');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -930,7 +919,7 @@
   // Select hello.txt and send it to the Trash, this file should not be removed
   // in between enabling and disabling the feature.
   await remoteCall.waitUntilSelected(appId, 'hello.txt');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -1006,7 +995,7 @@
   await remoteCall.waitAndClickElement(appId, '#file-list [file-name="B"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, '#file-list [file-name="B"]');
 
   // Navigate to /My files/Downloads and click the "A" directory.
@@ -1014,7 +1003,7 @@
   await remoteCall.waitAndClickElement(appId, '#file-list [file-name="A"]');
 
   // Delete item and wait for it to be removed (no dialog).
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, '#file-list [file-name="A"]');
 
   // Navigate to Trash and click the "B" directory of which the parent "A"
@@ -1045,7 +1034,7 @@
 
   // Select hello.txt and send it to the Trash.
   await remoteCall.waitAndClickElement(appId, fileSelector);
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, fileSelector);
 
   // Navigate to the Trash root.
@@ -1108,7 +1097,7 @@
 
   // Select folder A and send it to the Trash.
   await remoteCall.waitAndClickElement(appId, fileSelector);
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, fileSelector);
 
   // Navigate to the Trash root.
@@ -1162,7 +1151,7 @@
 
   // Select tera.zip and send it to the Trash.
   await remoteCall.waitAndClickElement(appId, fileSelector);
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, fileSelector);
 
   // Navigate to the Trash root.
@@ -1200,7 +1189,7 @@
   // Click blank space.
   await remoteCall.rightClickFileListBlankSpace(appId);
 
-  // Ensure the context menu is hidden.
+  // Ensure the context menu is not hidden.
   const contextMenuSelector = '#file-context-menu:not([hidden])';
   await remoteCall.waitForElement(appId, contextMenuSelector);
   // Ensure each infeasible action is disabled and hidden.
@@ -1235,7 +1224,7 @@
   // Select hello.txt and send it to the Trash, this file should not be removed
   // in between enabling and disabling the feature.
   await remoteCall.waitUntilSelected(appId, 'hello.txt');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="hello.txt"]');
 
@@ -1258,7 +1247,7 @@
 
   // Select and trash the file "world.ogv".
   await remoteCall.waitUntilSelected(appId, 'world.ogv');
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(
       appId, '#file-list [file-name="world.ogv"]');
 
@@ -1277,7 +1266,7 @@
 
   // Select hello.txt and send it to the Trash.
   await remoteCall.waitAndClickElement(appId, fileSelector);
-  await clickTrashButton(appId);
+  await remoteCall.clickTrashButton(appId);
   await remoteCall.waitForElementLost(appId, fileSelector);
 
   // Enable hidden files to be shown.
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index 028696a..04efa21 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -1138,4 +1138,15 @@
               items} and percentage=${percentage}`);
     });
   }
+
+  /**
+   * Clicks the enabled and visible move to trash button and ensures the delete
+   * button is hidden.
+   * @param {string} appId
+   */
+  async clickTrashButton(appId) {
+    await this.waitForElement(appId, '#delete-button[hidden]');
+    await this.waitAndClickElement(
+        appId, '#move-to-trash-button:not([hidden]):not([disabled])');
+  }
 }
diff --git a/ui/gfx/platform_font_ios.mm b/ui/gfx/platform_font_ios.mm
index 3d5f90cb..e23b56a 100644
--- a/ui/gfx/platform_font_ios.mm
+++ b/ui/gfx/platform_font_ios.mm
@@ -155,9 +155,9 @@
 
 void PlatformFontIOS::CalculateMetrics() {
   UIFont* font = base::mac::CFToNSCast(GetCTFont());
-  height_ = font.lineHeight;
-  ascent_ = font.ascender;
-  cap_height_ = font.capHeight;
+  height_ = ceil(font.lineHeight);
+  ascent_ = ceil(font.ascender);
+  cap_height_ = ceil(font.capHeight);
   average_width_ = [@"x" cr_sizeWithFont:font].width;
 }
 
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index b797278..bf57032 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -6429,7 +6429,7 @@
   // see: http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
   RenderText* render_text = GetRenderText();
   render_text->SetText(u"\uf8ff");
-#if BUILDFLAG(IS_APPLE)
+#if BUILDFLAG(IS_MAC)
   EXPECT_EQ(u"\uf8ff", render_text->GetDisplayText());
 #else
   EXPECT_EQ(u"\ufffd", render_text->GetDisplayText());
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 2f02d26..6ab2d68 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -625,7 +625,10 @@
   ]
 
   if (is_win) {
-    deps += [ "//media/base/win:test_support" ]
+    deps += [
+      "//media/base/win:test_support",
+      "//ui/base:pixel_diff_test_support",
+    ]
     libs = [ "dxguid.lib" ]
   }
 
diff --git a/ui/gl/DEPS b/ui/gl/DEPS
index bdc8c44..bedc2128 100644
--- a/ui/gl/DEPS
+++ b/ui/gl/DEPS
@@ -22,6 +22,8 @@
     "+ui/base/win/hidden_window.h",
   ],
   "dcomp_presenter_unittest.cc": [
+    "+ui/base/test/skia_gold_matching_algorithm.h",
+    "+ui/base/test/skia_gold_pixel_diff.h",
     "+ui/base/win/hidden_window.h",
     "+ui/platform_window",
   ],
diff --git a/ui/gl/dcomp_presenter_unittest.cc b/ui/gl/dcomp_presenter_unittest.cc
index 37c1fa2..eda0d20 100644
--- a/ui/gl/dcomp_presenter_unittest.cc
+++ b/ui/gl/dcomp_presenter_unittest.cc
@@ -7,15 +7,19 @@
 #include <wrl/client.h>
 #include <wrl/implements.h>
 
+#include "base/containers/flat_set.h"
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/test/power_monitor_test.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/win/windows_version.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/test/skia_gold_pixel_diff.h"
 #include "ui/base/win/hidden_window.h"
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/frame_data.h"
@@ -40,6 +44,8 @@
 namespace gl {
 namespace {
 
+constexpr const char* kSkiaGoldPixelDiffCorpus = "chrome-gpu-gtest";
+
 class TestPlatformDelegate : public ui::PlatformWindowDelegate {
  public:
   // ui::PlatformWindowDelegate implementation.
@@ -1437,6 +1443,133 @@
                                std::move(overlay));
 }
 
+class DCompPresenterSkiaGoldTest : public DCompPresenterPixelTest {
+ protected:
+  void SetUp() override {
+    DCompPresenterPixelTest::SetUp();
+
+    // |pixel_diff_| only needs to be initialized once per suite, but depends on
+    // | DCompPresenterTest::SetUp()| so is initialized here.
+    if (!pixel_diff_.has_value()) {
+      pixel_diff_.emplace();
+
+      ASSERT_TRUE(context_);
+      const ui::test::TestEnvironmentMap test_environment = {
+          {ui::test::TestEnvironmentKey::kSystemVersion,
+           base::win::OSInfo::GetInstance()->release_id()},
+          {ui::test::TestEnvironmentKey::kGpuDriverVendor,
+           context_->GetVersionInfo()->driver_vendor},
+          {ui::test::TestEnvironmentKey::kGpuDriverVersion,
+           context_->GetVersionInfo()->driver_version},
+          {ui::test::TestEnvironmentKey::kGlRenderer,
+           context_->GetGLRenderer()},
+
+      };
+
+      pixel_diff_->Init(::testing::UnitTest::GetInstance()
+                            ->current_test_info()
+                            ->test_suite_name(),
+                        kSkiaGoldPixelDiffCorpus, test_environment);
+    }
+  }
+
+  void TearDown() override {
+    DCompPresenterPixelTest::TearDown();
+    test_initialized_ = false;
+  }
+
+  void InitializeTest(const gfx::Size& window_size) {
+    ASSERT_FALSE(test_initialized_)
+        << "InitializeTest should only be called once per test";
+    test_initialized_ = true;
+
+    ResizeWindow(window_size);
+
+    capture_names_in_test_.clear();
+  }
+
+  void ResizeWindow(const gfx::Size& window_size) {
+    EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
+    EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
+    window_size_ = window_size;
+  }
+
+  // |capture_name| identifies this screenshot and is appended to the skia gold
+  // remote test name. Empty string is allowed, e.g. for tests that only have
+  // one screenshot.
+  // Tests should consider passing meaningful capture names if it helps make
+  // them easier to understand and debug.
+  // Unique capture names are required if a test checks multiple screenshots.
+  void PresentAndCheckScreenshot(
+      std::string capture_name = std::string(),
+      const base::Location& caller_location = FROM_HERE) {
+    ASSERT_TRUE(test_initialized_) << "Must call InitializeTest first";
+
+    if (capture_names_in_test_.contains(capture_name)) {
+      ADD_FAILURE_AT(caller_location.file_name(), caller_location.line_number())
+          << "Capture names must be unique in a test. Capture name \""
+          << capture_name << "\" is already used.";
+      return;
+    }
+    capture_names_in_test_.insert(capture_name);
+
+    PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
+
+    std::string screenshot_name =
+        ::testing::UnitTest::GetInstance()->current_test_info()->name();
+    if (!capture_name.empty()) {
+      screenshot_name = base::StringPrintf("%s/%s", screenshot_name.c_str(),
+                                           capture_name.c_str());
+    }
+
+    SkBitmap window_readback =
+        GLTestHelper::ReadBackWindow(window_.hwnd(), window_size_);
+    ASSERT_TRUE(pixel_diff_);
+    if (!pixel_diff_->CompareScreenshot(screenshot_name, window_readback)) {
+      ADD_FAILURE_AT(caller_location.file_name(), caller_location.line_number())
+          << "Screenshot mismatch for "
+          << (capture_name.empty() ? "(unnamed capture)" : capture_name);
+    }
+  }
+
+  const gfx::Size& current_window_size() const { return window_size_; }
+
+ private:
+  absl::optional<ui::test::SkiaGoldPixelDiff> pixel_diff_;
+
+  // |true|, if |InitializeTest| has been called.
+  bool test_initialized_ = false;
+
+  // The size of the window and screenshots, in pixels.
+  gfx::Size window_size_;
+
+  // The values of the |capture_name| parameter of |PresentAndCheckScreenshot|
+  // seen in the test so far.
+  base::flat_set<std::string> capture_names_in_test_;
+};
+
+TEST_F(DCompPresenterSkiaGoldTest, NonAxisPerservingTransform) {
+  InitializeTest(gfx::Size(100, 100));
+
+  InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack);
+
+  auto overlay = std::make_unique<DCLayerOverlayParams>();
+  overlay->content_rect = gfx::Rect(50, 50);
+  overlay->quad_rect = gfx::Rect(50, 50);
+  overlay->overlay_image =
+      CreateDCompSurface(gfx::Size(50, 50), SkColors::kRed);
+  overlay->z_order = 1;
+
+  // Center and partially rotate the overlay
+  overlay->transform.Translate(50, 50);
+  overlay->transform.Rotate(15);
+  overlay->transform.Translate(-25, -25);
+
+  EXPECT_TRUE(presenter_->ScheduleDCLayer(std::move(overlay)));
+
+  PresentAndCheckScreenshot();
+}
+
 class DCompPresenterBufferCountTest : public DCompPresenterTest,
                                       public testing::WithParamInterface<bool> {
  public:
diff --git a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
index 4ea6211..5bc8ed1 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
+++ b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
@@ -11,6 +11,7 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/synchronization/lock.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 01d3ab2..72ef342 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -80,7 +80,7 @@
 constexpr uint32_t kMaxWpPresentationVersion = 1;
 constexpr uint32_t kMaxWpViewporterVersion = 1;
 constexpr uint32_t kMaxTextInputManagerVersion = 1;
-constexpr uint32_t kMaxTextInputExtensionVersion = 8;
+constexpr uint32_t kMaxTextInputExtensionVersion = 10;
 constexpr uint32_t kMaxExplicitSyncVersion = 2;
 constexpr uint32_t kMaxAlphaCompositingVersion = 1;
 constexpr uint32_t kMaxXdgDecorationVersion = 1;
diff --git a/ui/ozone/platform/wayland/host/wayland_input_method_context.cc b/ui/ozone/platform/wayland/host/wayland_input_method_context.cc
index 34e51a2..d45293a 100644
--- a/ui/ozone/platform/wayland/host/wayland_input_method_context.cc
+++ b/ui/ozone/platform/wayland/host/wayland_input_method_context.cc
@@ -122,6 +122,33 @@
   return absl::nullopt;
 }
 
+// Returns the biggest range that is included in the |range|,
+// but whose start/end points are at the UTF-8 boundary.
+// If the given range is bigger than the given text_utf8,
+// it will be trimmed to the text_utf8 size.
+gfx::Range AdjustUtf8Alignment(base::StringPiece text_utf8, gfx::Range range) {
+  // Truncate the text to fit into the wayland message size and adjust indices
+  // of |selection_range|. Since the text is in UTF8 form, we need to adjust
+  // the text and selection range positions where all characters are valid.
+  //
+  // TODO(crbug.com/1214957): We should use base::i18n::BreakIterator
+  // to get the offsets and convert it into UTF8 form instead of using
+  // UTF8CharIterator.
+  base::i18n::UTF8CharIterator iter(text_utf8);
+  while (iter.array_pos() < range.start()) {
+    iter.Advance();
+  }
+  size_t adjusted_start = iter.array_pos();
+  size_t adjusted_end = adjusted_start;
+  while (iter.array_pos() <= range.end()) {
+    adjusted_end = iter.array_pos();
+    if (!iter.Advance()) {
+      break;
+    }
+  }
+  return {adjusted_start, adjusted_end};
+}
+
 struct OffsetText {
   std::string text;
   size_t offset;
@@ -177,30 +204,34 @@
         selection_range_utf8_center - kWaylandMessageDataMaxLength / 2;
   }
 
-  // Truncate the text to fit into the wayland message size and adjust indices
-  // of |selection_range|. Since the text is in UTF8 form, we need to adjust
-  // the text and selection range positions where all characters are valid.
-  //
-  // TODO(crbug.com/1214957): We should use base::i18n::BreakIterator
-  // to get the offsets and convert it into UTF8 form instead of using
-  // UTF8CharIterator.
-  base::i18n::UTF8CharIterator iter(text_utf8);
-  while (iter.array_pos() < start_index) {
-    iter.Advance();
-  }
-  size_t truncated_text_start = iter.array_pos();
-  size_t truncated_text_end;
-  while (iter.array_pos() <= start_index + kWaylandMessageDataMaxLength) {
-    truncated_text_end = iter.array_pos();
-    if (!iter.Advance()) {
-      break;
-    }
-  }
+  gfx::Range truncated_range = AdjustUtf8Alignment(
+      text_utf8,
+      gfx::Range(start_index, start_index + kWaylandMessageDataMaxLength));
 
-  return OffsetText{
-      std::string(text_utf8.substr(truncated_text_start,
-                                   truncated_text_end - truncated_text_start)),
-      truncated_text_start};
+  return OffsetText{std::string(text_utf8.substr(truncated_range.start(),
+                                                 truncated_range.length())),
+                    truncated_range.start()};
+}
+
+absl::optional<OffsetText> TrimSurroundingTextForExtension(
+    base::StringPiece text_utf8,
+    const base::span<size_t> offsets) {
+  // Heuristically, send leading/trailing (almost) 500 bytes in addition to
+  // offsets.
+  // Note: exo's implementation does not have 4000 bytes limit of the message.
+  static constexpr size_t kMaxSurroundingTextBytes = 500;
+  const auto& [min_offset, max_offset] = base::ranges::minmax(offsets);
+
+  size_t start_index =
+      min_offset - std::min(min_offset, kMaxSurroundingTextBytes);
+  size_t end_index = max_offset + kMaxSurroundingTextBytes;
+
+  gfx::Range truncated_range =
+      AdjustUtf8Alignment(text_utf8, gfx::Range(start_index, end_index));
+
+  return OffsetText{std::string(text_utf8.substr(truncated_range.start(),
+                                                 truncated_range.length())),
+                    truncated_range.start()};
 }
 
 // TODO(crbug.com/1402906): Add TrimSurroundingTextForExtension.
@@ -350,18 +381,23 @@
     const gfx::Range& selection_range,
     const absl::optional<GrammarFragment>& fragment,
     const absl::optional<AutocorrectInfo>& autocorrect) {
-  // TODO(crbug.com/1402906): Text range is not currently handled correctly.
-  surrounding_text_tracker_.Update(text, 0u, selection_range);
+  size_t utf16_offset = text_range.GetMin();
+  surrounding_text_tracker_.Update(text, utf16_offset, selection_range);
 
   if (!text_input_)
     return;
 
-  // Convert |text| and |selection_range| into UTF8 form.
-  std::vector<size_t> offsets_for_adjustment = {selection_range.start(),
-                                                selection_range.end()};
+  // Convert into UTF8 unit.
+  std::vector<size_t> offsets_for_adjustment = {
+      selection_range.start() - utf16_offset,
+      selection_range.end() - utf16_offset};
   if (fragment.has_value()) {
-    offsets_for_adjustment.push_back(fragment->range.start());
-    offsets_for_adjustment.push_back(fragment->range.end());
+    offsets_for_adjustment.push_back(fragment->range.start() - utf16_offset);
+    offsets_for_adjustment.push_back(fragment->range.end() - utf16_offset);
+  }
+  if (autocorrect.has_value()) {
+    offsets_for_adjustment.push_back(autocorrect->range.start() - utf16_offset);
+    offsets_for_adjustment.push_back(autocorrect->range.end() - utf16_offset);
   }
 
   std::string text_utf8 =
@@ -376,18 +412,24 @@
       static_cast<uint32_t>(offsets_for_adjustment[1])};
 
   auto trimmed =
-      TrimSurroundingTextForStandard(text_utf8, selection_range_utf8);
+      text_input_->HasOffsetSupport()
+          ? TrimSurroundingTextForExtension(text_utf8, offsets_for_adjustment)
+          : TrimSurroundingTextForStandard(text_utf8, selection_range_utf8);
   if (!trimmed.has_value()) {
     surrounding_text_tracker_.Reset();
     return;
   }
+
+  size_t extra_offset_utf16 =
+      base::UTF8ToUTF16(base::StringPiece(text_utf8).substr(0, trimmed->offset))
+          .length();
   text_utf8 = std::move(trimmed->text);
   surrounding_text_offset_ = trimmed->offset;
 
   if (fragment.has_value()) {
     // SetGrammarFragmentAtCursor must happen before SetSurroundingText to make
     // sure it is properly updated before IME needs it.
-    DCHECK_EQ(offsets_for_adjustment.size(), 4u);
+    DCHECK_GE(offsets_for_adjustment.size(), 4u);
     text_input_->SetGrammarFragmentAtCursor(GrammarFragment(
         gfx::Range(static_cast<uint32_t>(offsets_for_adjustment[2] -
                                          surrounding_text_offset_),
@@ -400,12 +442,25 @@
   }
 
   if (autocorrect.has_value()) {
+    size_t index = fragment.has_value() ? 4u : 2u;
     // Send the updated autocorrect information before surrounding text,
     // as surrounding text changes may trigger the IME to ask for the
     // autocorrect information.
-    text_input_->SetAutocorrectInfo(autocorrect->range, autocorrect->bounds);
+    gfx::Range autocorrect_range = autocorrect->range;
+    if (text_input_->HasOffsetSupport()) {
+      // The old implementation sent the original UTF-16 range as is, and
+      // the compositor also assumed it.
+      autocorrect_range =
+          gfx::Range(static_cast<uint32_t>(offsets_for_adjustment[index] -
+                                           surrounding_text_offset_),
+                     static_cast<uint32_t>(offsets_for_adjustment[index + 1] -
+                                           surrounding_text_offset_));
+    }
+
+    text_input_->SetAutocorrectInfo(autocorrect_range, autocorrect->bounds);
   }
 
+  text_input_->SetSurroundingTextOffsetUtf16(utf16_offset + extra_offset_utf16);
   gfx::Range relocated_selection_range(
       selection_range_utf8.start() - surrounding_text_offset_,
       selection_range_utf8.end() - surrounding_text_offset_);
@@ -544,8 +599,8 @@
 
   const gfx::Range new_selection_range =
       base::FeatureList::IsEnabled(features::kWaylandKeepSelectionFix)
-          ? gfx::Range(offsets[1], offsets[0])
-          : gfx::Range(offsets[0], offsets[1]);
+          ? gfx::Range(offsets[1] + utf16_offset, offsets[0] + utf16_offset)
+          : gfx::Range(offsets[0] + utf16_offset, offsets[1] + utf16_offset);
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   // Cursor position may be wrong on Lacros due to timing issue for some
   // scenario when surrounding text is longer than wayland message size
@@ -592,8 +647,8 @@
     return;
   }
 
-  if (selection.GetMin() < offsets_for_adjustment[0] ||
-      selection.GetMax() > offsets_for_adjustment[1]) {
+  if (selection.GetMin() < offsets_for_adjustment[0] + utf16_offset ||
+      selection.GetMax() > offsets_for_adjustment[1] + utf16_offset) {
     // The range is started after the selection, or ended before the selection,
     // which is not supported.
     LOG(DFATAL) << "The deletion range needs to cover whole selection range.";
@@ -602,8 +657,8 @@
 
   // Move by offset calculated in SetSurroundingText to adjust to the original
   // text place.
-  size_t before = selection.GetMin() - offsets_for_adjustment[0];
-  size_t after = offsets_for_adjustment[1] - selection.GetMax();
+  size_t before = selection.GetMin() - offsets_for_adjustment[0] - utf16_offset;
+  size_t after = offsets_for_adjustment[1] + utf16_offset - selection.GetMax();
 
   surrounding_text_tracker_.OnExtendSelectionAndDelete(before, after);
   ime_delegate_->OnDeleteSurroundingText(before, after);
@@ -665,8 +720,8 @@
                unused_composition_text] =
       surrounding_text_tracker_.predicted_state();
 
-  std::vector<size_t> selection_utf8_offsets = {selection.start(),
-                                                selection.end()};
+  std::vector<size_t> selection_utf8_offsets = {
+      selection.start() - utf16_offset, selection.end() - utf16_offset};
   std::string surrounding_text_utf8 = base::UTF16ToUTF8AndAdjustOffsets(
       surrounding_text, &selection_utf8_offsets);
 
@@ -724,44 +779,57 @@
   }
 
   surrounding_text_tracker_.OnSetCompositionFromExistingText(
-      gfx::Range(offsets[0], offsets[1]));
-  ime_delegate_->OnSetPreeditRegion(gfx::Range(offsets[0], offsets[1]),
-                                    ime_text_spans);
+      gfx::Range(offsets[0] + utf16_offset, offsets[1] + utf16_offset));
+  ime_delegate_->OnSetPreeditRegion(
+      gfx::Range(offsets[0] + utf16_offset, offsets[1] + utf16_offset),
+      ime_text_spans);
 }
 
 void WaylandInputMethodContext::OnClearGrammarFragments(
     const gfx::Range& range) {
-  std::u16string surrounding_text =
-      surrounding_text_tracker_.predicted_state().surrounding_text;
+  const auto& [surrounding_text, utf16_offset, selection, composition] =
+      surrounding_text_tracker_.predicted_state();
 
   std::vector<size_t> offsets = {range.start() + surrounding_text_offset_,
                                  range.end() + surrounding_text_offset_};
   base::UTF8ToUTF16AndAdjustOffsets(base::UTF16ToUTF8(surrounding_text),
                                     &offsets);
-  ime_delegate_->OnClearGrammarFragments(gfx::Range(
-      static_cast<uint32_t>(offsets[0]), static_cast<uint32_t>(offsets[1])));
+  ime_delegate_->OnClearGrammarFragments(
+      gfx::Range(static_cast<uint32_t>(offsets[0]) + utf16_offset,
+                 static_cast<uint32_t>(offsets[1]) + utf16_offset));
 }
 
 void WaylandInputMethodContext::OnAddGrammarFragment(
     const GrammarFragment& fragment) {
-  std::u16string surrounding_text =
-      surrounding_text_tracker_.predicted_state().surrounding_text;
+  const auto& [surrounding_text, utf16_offset, selection, composition] =
+      surrounding_text_tracker_.predicted_state();
 
   std::vector<size_t> offsets = {
       fragment.range.start() + surrounding_text_offset_,
       fragment.range.end() + surrounding_text_offset_};
   base::UTF8ToUTF16AndAdjustOffsets(base::UTF16ToUTF8(surrounding_text),
                                     &offsets);
-  ime_delegate_->OnAddGrammarFragment(
-      {GrammarFragment(gfx::Range(static_cast<uint32_t>(offsets[0]),
-                                  static_cast<uint32_t>(offsets[1])),
-                       fragment.suggestion)});
+  ime_delegate_->OnAddGrammarFragment({GrammarFragment(
+      gfx::Range(static_cast<uint32_t>(offsets[0]) + utf16_offset,
+                 static_cast<uint32_t>(offsets[1]) + utf16_offset),
+      fragment.suggestion)});
 }
 
 void WaylandInputMethodContext::OnSetAutocorrectRange(const gfx::Range& range) {
+  if (range.is_empty()) {
+    ime_delegate_->OnSetAutocorrectRange(range);
+    return;
+  }
+
+  const auto& [surrounding_text, utf16_offset, selection, composition] =
+      surrounding_text_tracker_.predicted_state();
+  std::vector<size_t> offsets = {range.start() + surrounding_text_offset_,
+                                 range.end() + surrounding_text_offset_};
+  base::UTF8ToUTF16AndAdjustOffsets(base::UTF16ToUTF8(surrounding_text),
+                                    &offsets);
   ime_delegate_->OnSetAutocorrectRange(
-      gfx::Range(range.start() + surrounding_text_offset_,
-                 range.end() + surrounding_text_offset_));
+      gfx::Range(static_cast<uint32_t>(offsets[0]) + utf16_offset,
+                 static_cast<uint32_t>(offsets[1]) + utf16_offset));
 }
 
 void WaylandInputMethodContext::OnSetVirtualKeyboardOccludedBounds(
diff --git a/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc b/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc
index 5e87e8a4..ce9132a 100644
--- a/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc
@@ -249,6 +249,10 @@
     SetUpInternal();
   }
 
+  wl::TestZcrTextInputExtensionV1::Version GetApiVersion() {
+    return GetParam().text_input_extension_version;
+  }
+
  protected:
   void SetUpInternal() {
     input_method_context_delegate_ =
@@ -289,8 +293,20 @@
 INSTANTIATE_TEST_SUITE_P(
     TextInputExtensionLatestVersion,
     WaylandInputMethodContextTest,
-    ::testing::Values(wl::ServerConfig{.use_ime_keep_selection_fix = true},
-                      wl::ServerConfig{.use_ime_keep_selection_fix = false}));
+    ::testing::Values(
+        wl::ServerConfig{
+            .text_input_extension_version =
+                wl::TestZcrTextInputExtensionV1::Version::kV8,
+            .use_ime_keep_selection_fix = true,
+        },
+        wl::ServerConfig{
+            .text_input_extension_version =
+                wl::TestZcrTextInputExtensionV1::Version::kV8,
+            .use_ime_keep_selection_fix = false,
+        },
+        wl::ServerConfig{.use_ime_keep_selection_fix = true},
+        wl::ServerConfig{.use_ime_keep_selection_fix = false}));
+
 INSTANTIATE_TEST_SUITE_P(
     TextInputExtensionV7,
     WaylandInputMethodContextOldServerTest,
@@ -513,17 +529,25 @@
   const std::u16string text(5000, u'あ');
   constexpr gfx::Range range(2800, 3200);
 
-  // The text sent as wayland protocol must be at most 4000 byte and long
-  // enough in the limitation.
-  const std::string kExpectedSentText(
-      base::UTF16ToUTF8(std::u16string(1332, u'あ')));
-  // The selection range must be relocated accordingly to the sent text.
-  constexpr gfx::Range kExpectedSentRange(1398, 2598);
+  std::string expected_sent_text;
+  gfx::Range expected_sent_range;
+  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+    // In the old protocol, the text sent as wayland protocol must be at most
+    // 4000 byte and long enough in the limitation.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(1332, u'あ'));
+    // The selection range must be relocated accordingly to the sent text.
+    expected_sent_range = gfx::Range(1398, 2598);
+  } else {
+    // In the new protocol, the whole selection text with 500 bytes buffers are
+    // sent.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(732, u'あ'));
+    expected_sent_range = gfx::Range(498, 1698);
+  }
 
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+  PostToServerAndWait([expected_sent_text, expected_sent_range](
                           wl::TestWaylandServerThread* server) {
     EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
-                SetSurroundingText(kExpectedSentText, kExpectedSentRange))
+                SetSurroundingText(expected_sent_text, expected_sent_range))
         .Times(1);
   });
 
@@ -537,14 +561,14 @@
   connection_->Flush();
 
   PostToServerAndWait(
-      [kExpectedSentRange](wl::TestWaylandServerThread* server) {
+      [expected_sent_range](wl::TestWaylandServerThread* server) {
         auto* text_input = server->text_input_manager_v1()->text_input();
         Mock::VerifyAndClearExpectations(text_input);
 
         // Test OnDeleteSurroundingText with this input.
         zwp_text_input_v1_send_delete_surrounding_text(
-            text_input->resource(), kExpectedSentRange.start(),
-            kExpectedSentRange.length());
+            text_input->resource(), expected_sent_range.start(),
+            expected_sent_range.length());
       });
 
   EXPECT_EQ(
@@ -561,17 +585,25 @@
   const std::u16string text(5000, u'あ');
   constexpr gfx::Range range(0, 500);
 
-  // The text sent as wayland protocol must be at most 4000 byte and large
-  // enough in the limitation.
-  const std::string kExpectedSentText(
-      base::UTF16ToUTF8(std::u16string(1333, u'あ')));
-  // The selection range must be relocated accordingly to the sent text.
-  constexpr gfx::Range kExpectedSentRange(0, 1500);
+  std::string expected_sent_text;
+  gfx::Range expected_sent_range;
+  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+    // In old protocol, the text sent as wayland protocol must be at most 4000
+    // byte and large enough in the limitation.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(1333, u'あ'));
+    // The selection range must be relocated accordingly to the sent text.
+    expected_sent_range = gfx::Range(0, 1500);
+  } else {
+    // In the new protocol, whole selection range + at most 500 bytes buffers
+    // are sent.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(666, u'あ'));
+    expected_sent_range = gfx::Range(0, 1500);
+  }
 
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+  PostToServerAndWait([expected_sent_text, expected_sent_range](
                           wl::TestWaylandServerThread* server) {
     EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
-                SetSurroundingText(kExpectedSentText, kExpectedSentRange))
+                SetSurroundingText(expected_sent_text, expected_sent_range))
         .Times(1);
   });
 
@@ -585,14 +617,14 @@
   connection_->Flush();
 
   PostToServerAndWait(
-      [kExpectedSentRange](wl::TestWaylandServerThread* server) {
+      [expected_sent_range](wl::TestWaylandServerThread* server) {
         auto* text_input = server->text_input_manager_v1()->text_input();
         Mock::VerifyAndClearExpectations(text_input);
 
         // Test OnDeleteSurroundingText with this input.
         zwp_text_input_v1_send_delete_surrounding_text(
-            text_input->resource(), kExpectedSentRange.start(),
-            kExpectedSentRange.length());
+            text_input->resource(), expected_sent_range.start(),
+            expected_sent_range.length());
       });
 
   EXPECT_EQ(
@@ -610,17 +642,25 @@
   const std::u16string text(5000, u'あ');
   constexpr gfx::Range range(4500, 5000);
 
-  // The text sent as wayland protocol must be at most 4000 byte and large
-  // enough in the limitation.
-  const std::string kExpectedSentText(
-      base::UTF16ToUTF8(std::u16string(1333, u'あ')));
-  // The selection range must be relocated accordingly to the sent text.
-  constexpr gfx::Range kExpectedSentRange(2499, 3999);
+  std::string expected_sent_text;
+  gfx::Range expected_sent_range;
+  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+    // In the old protocol, the text sent as wayland protocol must be at most
+    // 4000 byte and large enough in the limitation.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(1333, u'あ'));
+    // The selection range must be relocated accordingly to the sent text.
+    expected_sent_range = gfx::Range(2499, 3999);
+  } else {
+    // In the new protocol, whole selection + at most 500 bytes buffers are
+    // sent.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(666, u'あ'));
+    expected_sent_range = gfx::Range(498, 1998);
+  }
 
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+  PostToServerAndWait([expected_sent_text, expected_sent_range](
                           wl::TestWaylandServerThread* server) {
     EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
-                SetSurroundingText(kExpectedSentText, kExpectedSentRange))
+                SetSurroundingText(expected_sent_text, expected_sent_range))
         .Times(1);
   });
 
@@ -634,14 +674,14 @@
   connection_->Flush();
 
   PostToServerAndWait(
-      [kExpectedSentRange](wl::TestWaylandServerThread* server) {
+      [expected_sent_range](wl::TestWaylandServerThread* server) {
         auto* text_input = server->text_input_manager_v1()->text_input();
         Mock::VerifyAndClearExpectations(text_input);
 
         // Test OnDeleteSurroundingText with this input.
         zwp_text_input_v1_send_delete_surrounding_text(
-            text_input->resource(), kExpectedSentRange.start(),
-            kExpectedSentRange.length());
+            text_input->resource(), expected_sent_range.start(),
+            expected_sent_range.length());
       });
 
   EXPECT_EQ(
@@ -658,29 +698,57 @@
   const std::u16string text(5000, u'あ');
   constexpr gfx::Range range(1000, 4000);
 
-  // set_surrounding_text request should be skipped when the selection range in
-  // UTF8 form is longer than 4000 byte.
-  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
-    EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
-                SetSurroundingText(_, _))
-        .Times(0);
-  });
+  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+    // set_surrounding_text request should be skipped when the selection range
+    // in UTF8 form is longer than 4000 byte.
+    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
+      EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
+                  SetSurroundingText(_, _))
+          .Times(0);
+    });
 
-  input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
-                                            absl::nullopt, absl::nullopt);
-  // Predicted state in SurroundingTextTracker is reset when the range is longer
-  // than wayland message size maximum.
-  EXPECT_EQ(
-      input_method_context_->predicted_state_for_testing().surrounding_text,
-      u"");
-  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
-            gfx::Range(0));
-  connection_->Flush();
+    input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
+                                              absl::nullopt, absl::nullopt);
+    // Predicted state in SurroundingTextTracker is reset when the range is
+    // longer than wayland message size maximum.
+    EXPECT_EQ(
+        input_method_context_->predicted_state_for_testing().surrounding_text,
+        u"");
+    EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
+              gfx::Range(0));
+    connection_->Flush();
 
-  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
-    Mock::VerifyAndClearExpectations(
-        server->text_input_manager_v1()->text_input());
-  });
+    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
+      Mock::VerifyAndClearExpectations(
+          server->text_input_manager_v1()->text_input());
+    });
+  } else {
+    // In the new protocol, we can send large selection range.
+    const std::string kExpectedSentText =
+        base::UTF16ToUTF8(std::u16string(3332, u'あ'));
+    constexpr gfx::Range kExpectedSentRange(498, 9498);
+
+    PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+                            wl::TestWaylandServerThread* server) {
+      EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
+                  SetSurroundingText(kExpectedSentText, kExpectedSentRange))
+          .Times(1);
+    });
+
+    input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
+                                              absl::nullopt, absl::nullopt);
+    EXPECT_EQ(
+        input_method_context_->predicted_state_for_testing().surrounding_text,
+        text);
+    EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
+              range);
+    connection_->Flush();
+
+    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
+      Mock::VerifyAndClearExpectations(
+          server->text_input_manager_v1()->text_input());
+    });
+  }
 }
 
 TEST_P(WaylandInputMethodContextTest,
@@ -719,25 +787,37 @@
   const std::u16string text(5000, u'あ');
   constexpr gfx::Range range(2800, 3200);
 
-  // The text sent as wayland protocol must be at most 4000 byte and long
-  // enough in the limitation.
-  const std::string kExpectedSentText(
-      base::UTF16ToUTF8(std::u16string(1332, u'あ')));
-  // The selection range must be relocated accordingly to the sent text.
-  constexpr gfx::Range kExpectedSentRange(1398, 2598);
+  std::string expected_sent_text;
+  gfx::Range expected_sent_range;
+  gfx::Range expected_fragment_range;
+  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+    // In the old protocol, the text sent as wayland protocol must be at
+    // most 4000 byte and long enough in the limitation.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(1332, u'あ'));
+    // The selection range must be relocated accordingly to the sent text.
+    expected_sent_range = gfx::Range(1398, 2598);
+    expected_fragment_range = gfx::Range(1098, 1128);
+  } else {
+    // In the new protocol, whole selection range and grammar fragment are
+    // sent with at most 500 bytes buffer.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(832, u'あ'));
+    expected_sent_range = gfx::Range(798, 1998);
+    expected_fragment_range = gfx::Range(498, 528);
+  }
 
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
-                          wl::TestWaylandServerThread* server) {
-    EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
-                SetSurroundingText(kExpectedSentText, kExpectedSentRange))
-        .Times(1);
-    EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
-                SetGrammarFragmentAtCursor(gfx::Range(1098, 1128), "abc"))
-        .Times(1);
-  });
+  PostToServerAndWait(
+      [expected_sent_text, expected_sent_range,
+       expected_fragment_range](wl::TestWaylandServerThread* server) {
+        EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
+                    SetSurroundingText(expected_sent_text, expected_sent_range))
+            .Times(1);
+        EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
+                    SetGrammarFragmentAtCursor(expected_fragment_range, "abc"))
+            .Times(1);
+      });
 
   input_method_context_->SetSurroundingText(
-      text, gfx::Range(0, 50), range,
+      text, gfx::Range(0, 5000), range,
       GrammarFragment(gfx::Range(2700, 2710), "abc"), absl::nullopt);
   EXPECT_EQ(
       input_method_context_->predicted_state_for_testing().surrounding_text,
@@ -755,16 +835,24 @@
   const std::string kExpectedSentText(base::UTF16ToUTF8(text));
   constexpr gfx::Range kExpectedSentRange(60, 90);
 
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+  PostToServerAndWait([this, kExpectedSentText, kExpectedSentRange](
                           wl::TestWaylandServerThread* server) {
     // The text and range sent as wayland protocol must be same to the original
     // text and range where the original text is shorter than 4000 byte.
     EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
                 SetSurroundingText(kExpectedSentText, kExpectedSentRange))
         .Times(1);
-    // Note: No conversion of range for autocorrect now.
+    gfx::Range autocorrect_range;
+    if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+      // In older protocol, the autocorrection range is not converted.
+      autocorrect_range = gfx::Range(15, 18);
+    } else {
+      // In new protocol, it is byte offsets within the surrounding text.
+      autocorrect_range = gfx::Range(45, 54);
+    }
+
     EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
-                SetAutocorrectInfo(gfx::Range(15, 18), gfx::Rect(10, 20)));
+                SetAutocorrectInfo(autocorrect_range, gfx::Rect(10, 20)));
   });
 
   input_method_context_->SetSurroundingText(
@@ -1132,17 +1220,27 @@
   const std::u16string text(5000, u'あ');
   constexpr gfx::Range range(4000, 4500);
 
-  // Text longer than 4000 bytes is trimmed to meet the limitation.
-  // Selection range is also adjusted by the trimmed text before sendin to Exo.
-  const std::string kExpectedSentText(
-      base::UTF16ToUTF8(std::u16string(1332, u'あ')));
-  constexpr gfx::Range kExpectedSentRange(1248, 2748);
+  std::string expected_sent_text;
+  gfx::Range expected_sent_range;
+  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
+    // In old protocol, even if the range covers, the surrounding text
+    // longer than 4000 bytes is trimmed to meet the limitation.
+    // Selection range is also adjusted by the trimmed text before sendin to
+    // Exo.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(1332, u'あ'));
+    expected_sent_range = gfx::Range(1248, 2748);
+  } else {
+    // In new protocol, the surrounding text is trimmed around selection with
+    // at most 500 bytes buffer.
+    expected_sent_text = base::UTF16ToUTF8(std::u16string(832, u'あ'));
+    expected_sent_range = gfx::Range(498, 1998);
+  }
 
   // SetSurroundingText should be called in UTF-8.
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+  PostToServerAndWait([expected_sent_text, expected_sent_range](
                           wl::TestWaylandServerThread* server) {
     EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
-                SetSurroundingText(kExpectedSentText, kExpectedSentRange));
+                SetSurroundingText(expected_sent_text, expected_sent_range));
   });
   input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
                                             absl::nullopt, absl::nullopt);
@@ -1153,20 +1251,21 @@
             range);
   connection_->Flush();
 
-  PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
+  PostToServerAndWait([expected_sent_text, expected_sent_range](
                           wl::TestWaylandServerThread* server) {
     auto* text_input = server->text_input_manager_v1()->text_input();
     Mock::VerifyAndClearExpectations(text_input);
 
-    const auto expected_sent_range =
-        GetParam().use_ime_keep_selection_fix
-            ? gfx::Range(kExpectedSentRange.end(), kExpectedSentRange.start())
-            : kExpectedSentRange;
+    gfx::Range range = expected_sent_range;
+    if (GetParam().use_ime_keep_selection_fix) {
+      range =
+          gfx::Range(expected_sent_range.end(), expected_sent_range.start());
+    }
+
     zwp_text_input_v1_send_cursor_position(text_input->resource(),
-                                           expected_sent_range.start(),
-                                           expected_sent_range.end());
+                                           range.start(), range.end());
     zwp_text_input_v1_send_commit_string(text_input->resource(), 0,
-                                         kExpectedSentText.c_str());
+                                         expected_sent_text.c_str());
   });
 
   EXPECT_TRUE(
diff --git a/ui/ozone/platform/wayland/host/wayland_surface.h b/ui/ozone/platform/wayland/host/wayland_surface.h
index 6a45649..963b09d 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface.h
+++ b/ui/ozone/platform/wayland/host/wayland_surface.h
@@ -12,6 +12,7 @@
 #include "base/containers/flat_map.h"
 #include "base/files/scoped_file.h"
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h
index 0b6e1d7d..d3c618d 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h
@@ -11,6 +11,7 @@
 #include <string>
 
 #include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-forward.h"
diff --git a/ui/ozone/platform/wayland/host/zwp_text_input_wrapper.h b/ui/ozone/platform/wayland/host/zwp_text_input_wrapper.h
index bbbf66d2..2181aa7 100644
--- a/ui/ozone/platform/wayland/host/zwp_text_input_wrapper.h
+++ b/ui/ozone/platform/wayland/host/zwp_text_input_wrapper.h
@@ -128,6 +128,8 @@
   virtual void SetCursorRect(const gfx::Rect& rect) = 0;
   virtual void SetSurroundingText(const std::string& text,
                                   const gfx::Range& selection_range) = 0;
+  virtual bool HasOffsetSupport() const = 0;
+  virtual void SetSurroundingTextOffsetUtf16(uint32_t offset_utf16) = 0;
   virtual void SetContentType(ui::TextInputType type,
                               ui::TextInputMode mode,
                               uint32_t flags,
diff --git a/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.cc b/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.cc
index a2f0ead..4c036fa7 100644
--- a/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.cc
+++ b/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.cc
@@ -219,6 +219,20 @@
       obj_.get(), text.c_str(), selection_range.start(), selection_range.end());
 }
 
+bool ZWPTextInputWrapperV1::HasOffsetSupport() const {
+  return extended_obj_.get() &&
+         wl::get_version_of_object(extended_obj_.get()) >=
+             ZCR_EXTENDED_TEXT_INPUT_V1_SET_SURROUNDING_TEXT_OFFSET_UTF16_SINCE_VERSION;
+}
+
+void ZWPTextInputWrapperV1::SetSurroundingTextOffsetUtf16(
+    uint32_t offset_utf16) {
+  if (HasOffsetSupport()) {
+    zcr_extended_text_input_v1_set_surrounding_text_offset_utf16(
+        extended_obj_.get(), offset_utf16);
+  }
+}
+
 void ZWPTextInputWrapperV1::SetContentType(ui::TextInputType type,
                                            ui::TextInputMode mode,
                                            uint32_t flags,
diff --git a/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.h b/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.h
index b11e941..45cf5f3 100644
--- a/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.h
+++ b/ui/ozone/platform/wayland/host/zwp_text_input_wrapper_v1.h
@@ -49,6 +49,8 @@
   void SetCursorRect(const gfx::Rect& rect) override;
   void SetSurroundingText(const std::string& text,
                           const gfx::Range& selection_range) override;
+  bool HasOffsetSupport() const override;
+  void SetSurroundingTextOffsetUtf16(uint32_t offset_utf16) override;
   void SetContentType(TextInputType type,
                       TextInputMode mode,
                       uint32_t flags,
diff --git a/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.cc b/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.cc
index 6a67346..5989260 100644
--- a/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.cc
+++ b/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.cc
@@ -60,6 +60,20 @@
   GetUserDataAs<MockZcrExtendedTextInput>(resource)->SetFocusReason(reason);
 }
 
+void SetSurroundingTextSupport(wl_client* client,
+                               wl_resource* resource,
+                               uint32_t support) {
+  GetUserDataAs<MockZcrExtendedTextInput>(resource)->SetSurroundingTextSupport(
+      support);
+}
+
+void SetSurroundingTextOffsetUtf16(wl_client* client,
+                                   wl_resource* resource,
+                                   uint32_t offset) {
+  GetUserDataAs<MockZcrExtendedTextInput>(resource)
+      ->SetSurroundingTextOffsetUtf16(offset);
+}
+
 }  // namespace
 
 const struct zcr_extended_text_input_v1_interface
@@ -71,6 +85,8 @@
         &FinalizeVirtualKeyboardChanges,  // finalize_virtual_keyboard_changes
         &SetFocusReason,                  // set_focus_reason
         &SetInputType,                    // set_input_type
+        &SetSurroundingTextSupport,       // set_surrounding_text_support
+        &SetSurroundingTextOffsetUtf16,   // set_surrounding_text_offset_utf16
 };
 
 MockZcrExtendedTextInput::MockZcrExtendedTextInput(wl_resource* resource)
diff --git a/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.h b/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.h
index 9b8ad15c..ed6c0732 100644
--- a/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.h
+++ b/ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.h
@@ -49,6 +49,8 @@
               (const gfx::Range& range, const gfx::Rect& bounds));
   MOCK_METHOD(void, FinalizeVirtualKeyboardChanges, ());
   MOCK_METHOD(void, SetFocusReason, (uint32_t reason));
+  MOCK_METHOD(void, SetSurroundingTextSupport, (uint32_t support));
+  MOCK_METHOD(void, SetSurroundingTextOffsetUtf16, (uint32_t offset));
 };
 
 }  // namespace wl
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
index 3e50ac55..a274a03 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -58,7 +58,7 @@
 
 struct ServerConfig {
   TestZcrTextInputExtensionV1::Version text_input_extension_version =
-      TestZcrTextInputExtensionV1::Version::kV8;
+      TestZcrTextInputExtensionV1::Version::kV10;
   TestCompositor::Version compositor_version = TestCompositor::Version::kV4;
   PrimarySelectionProtocol primary_selection_protocol =
       PrimarySelectionProtocol::kNone;
diff --git a/ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h b/ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h
index 348cd0ce..d3300518 100644
--- a/ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h
+++ b/ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h
@@ -23,6 +23,7 @@
   enum class Version : uint32_t {
     kV7 = 7,
     kV8 = 8,
+    kV10 = 10,
   };
   explicit TestZcrTextInputExtensionV1(Version version);
   TestZcrTextInputExtensionV1(const TestZcrTextInputExtensionV1&) = delete;
diff --git a/ui/shell_dialogs/BUILD.gn b/ui/shell_dialogs/BUILD.gn
index 5eb912e..266caf6 100644
--- a/ui/shell_dialogs/BUILD.gn
+++ b/ui/shell_dialogs/BUILD.gn
@@ -115,6 +115,10 @@
     libs = [ "jnigraphics" ]
   }
 
+  if (is_apple) {
+    configs += [ "//build/config/compiler:enable_arc" ]
+  }
+
   if (is_mac) {
     frameworks = [
       "CoreServices.framework",
@@ -154,6 +158,10 @@
     "select_file_dialog_unittest.cc",
   ]
 
+  if (is_apple) {
+    configs += [ "//build/config/compiler:enable_arc" ]
+  }
+
   if (is_mac) {
     sources += [ "select_file_dialog_mac_unittest.mm" ]
     weak_frameworks = [
diff --git a/ui/shell_dialogs/select_file_dialog_ios.mm b/ui/shell_dialogs/select_file_dialog_ios.mm
index e38bdd2..15c3b63 100644
--- a/ui/shell_dialogs/select_file_dialog_ios.mm
+++ b/ui/shell_dialogs/select_file_dialog_ios.mm
@@ -15,15 +15,18 @@
 #include "base/strings/sys_string_conversions.h"
 #include "ui/shell_dialogs/select_file_policy.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface NativeFileDialog : NSObject <UIDocumentPickerDelegate> {
  @private
   base::WeakPtr<ui::SelectFileDialogImpl> _dialog;
-  UIViewController* _viewController;
+  UIViewController* __weak _viewController;
   bool _allowMultipleFiles;
   void* _params;
-  base::scoped_nsobject<UIDocumentPickerViewController>
-      _documentPickerController;
-  base::scoped_nsobject<NSArray<UTType*>> _fileUTTypeLists;
+  UIDocumentPickerViewController* __strong _documentPickerController;
+  NSArray<UTType*>* __strong _fileUTTypeLists;
   bool _allowsOtherFileTypes;
 }
 
@@ -55,23 +58,22 @@
   _viewController = viewController;
   _allowMultipleFiles = allowMultipleFiles;
   _params = params;
-  _fileUTTypeLists.reset([fileUTTypeLists retain]);
+  _fileUTTypeLists = fileUTTypeLists;
   _allowsOtherFileTypes = allowsOtherFileTypes;
   return self;
 }
 
 - (void)dealloc {
-  [_documentPickerController setDelegate:nil];
-  [super dealloc];
+  _documentPickerController.delegate = nil;
 }
 
 - (void)showFilePickerMenu {
   NSArray* documentTypes =
       _allowsOtherFileTypes ? @[ UTTypeItem ] : _fileUTTypeLists;
-  _documentPickerController.reset([[UIDocumentPickerViewController alloc]
-      initForOpeningContentTypes:documentTypes]);
-  [_documentPickerController setAllowsMultipleSelection:_allowMultipleFiles];
-  [_documentPickerController setDelegate:self];
+  _documentPickerController = [[UIDocumentPickerViewController alloc]
+      initForOpeningContentTypes:documentTypes];
+  _documentPickerController.allowsMultipleSelection = _allowMultipleFiles;
+  _documentPickerController.delegate = self;
 
   UIViewController* currentViewController = _viewController;
   [currentViewController presentViewController:_documentPickerController
@@ -154,10 +156,8 @@
   has_multiple_file_type_choices_ =
       SelectFileDialog::SELECT_OPEN_MULTI_FILE == type;
   bool allows_other_file_types = false;
-  NSMutableArray /*<UTType*>*/* file_uttype_lists = [NSMutableArray array];
-  for (size_t i = 0; i < file_types->extensions.size(); ++i) {
-    const std::vector<base::FilePath::StringType>& ext_list =
-        file_types->extensions[i];
+  NSMutableArray<UTType*>* file_uttype_lists = [NSMutableArray array];
+  for (const auto& ext_list : file_types->extensions) {
     for (const base::FilePath::StringType& ext : ext_list) {
       UTType* uttype =
           [UTType typeWithFilenameExtension:base::SysUTF8ToNSString(ext)];
@@ -175,13 +175,13 @@
   }
 
   UIViewController* controller = gfx_window.rootViewController;
-  native_file_dialog_.reset([[NativeFileDialog alloc]
-            initWithDialog:weak_factory_.GetWeakPtr()
-            viewController:controller
-        allowMultipleFiles:has_multiple_file_type_choices_
-                    params:params
-           fileUTTypeLists:file_uttype_lists
-      allowsOtherFileTypes:allows_other_file_types]);
+  native_file_dialog_ =
+      [[NativeFileDialog alloc] initWithDialog:weak_factory_.GetWeakPtr()
+                                viewController:controller
+                            allowMultipleFiles:has_multiple_file_type_choices_
+                                        params:params
+                               fileUTTypeLists:file_uttype_lists
+                          allowsOtherFileTypes:allows_other_file_types];
   [native_file_dialog_ showFilePickerMenu];
 }
 
diff --git a/ui/shell_dialogs/select_file_dialog_mac.mm b/ui/shell_dialogs/select_file_dialog_mac.mm
index 0b8aaa3..7c7a929 100644
--- a/ui/shell_dialogs/select_file_dialog_mac.mm
+++ b/ui/shell_dialogs/select_file_dialog_mac.mm
@@ -16,6 +16,10 @@
 #include "ui/shell_dialogs/select_file_policy.h"
 #include "url/gurl.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using remote_cocoa::mojom::SelectFileDialogType;
 using remote_cocoa::mojom::SelectFileTypeInfo;
 using remote_cocoa::mojom::SelectFileTypeInfoPtr;
diff --git a/ui/shell_dialogs/select_file_dialog_mac_unittest.mm b/ui/shell_dialogs/select_file_dialog_mac_unittest.mm
index 65c8c2f..b7fb023 100644
--- a/ui/shell_dialogs/select_file_dialog_mac_unittest.mm
+++ b/ui/shell_dialogs/select_file_dialog_mac_unittest.mm
@@ -21,8 +21,13 @@
 #include "components/remote_cocoa/app_shim/select_file_dialog_bridge.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
 #include "ui/shell_dialogs/select_file_policy.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 #define EXPECT_EQ_BOOL(a, b) \
   EXPECT_EQ(static_cast<bool>(a), static_cast<bool>(b))
 
@@ -48,7 +53,7 @@
 namespace ui::test {
 
 // Helper test base to initialize SelectFileDialogImpl.
-class SelectFileDialogMacTest : public ::testing::Test,
+class SelectFileDialogMacTest : public PlatformTest,
                                 public SelectFileDialog::Listener {
  public:
   SelectFileDialogMacTest()
@@ -78,18 +83,17 @@
   // Helper method to create a dialog with the given `args`. Returns the created
   // NSSavePanel.
   NSSavePanel* SelectFileWithParams(FileDialogArguments args) {
-    base::scoped_nsobject<NSWindow> parent_window([[NSWindow alloc]
-        initWithContentRect:NSMakeRect(0, 0, 100, 100)
-                  styleMask:NSWindowStyleMaskTitled
-                    backing:NSBackingStoreBuffered
-                      defer:NO]);
-    [parent_window setReleasedWhenClosed:NO];
+    NSWindow* parent_window =
+        [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
+                                    styleMask:NSWindowStyleMaskTitled
+                                      backing:NSBackingStoreBuffered
+                                        defer:NO];
+    parent_window.releasedWhenClosed = NO;
     parent_windows_.push_back(parent_window);
 
     dialog_->SelectFile(args.type, args.title, args.default_path,
                         args.file_types, args.file_type_index,
-                        args.default_extension, parent_window.get(),
-                        args.params);
+                        args.default_extension, parent_window, args.params);
 
     // At this point, the Mojo IPC to show the dialog is queued up. Spin the
     // message loop to get the Mojo IPC to happen.
@@ -138,7 +142,7 @@
  private:
   scoped_refptr<SelectFileDialogImpl> dialog_;
 
-  std::vector<base::scoped_nsobject<NSWindow>> parent_windows_;
+  std::vector<NSWindow*> parent_windows_;
 };
 
 class SelectFileDialogMacOpenAndSaveTest
@@ -537,34 +541,4 @@
   EXPECT_TRUE(panel);
 }
 
-// Test to ensure lifetime is sound if a reference to
-// the panel outlives the delegate.
-TEST_F(SelectFileDialogMacTest, Lifetime) {
-  base::scoped_nsobject<NSSavePanel> panel;
-  @autoreleasepool {
-    FileDialogArguments args;
-    // Set a type (Save dialogs do not have a delegate).
-    args.type = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
-    panel.reset([SelectFileWithParams(args) retain]);
-
-    EXPECT_TRUE([panel isVisible]);
-    EXPECT_NE(nil, [panel delegate]);
-
-    // Newer versions of AppKit (>= 10.13) appear to clear out weak delegate
-    // pointers when dealloc is called on the delegate. Put a ref into the
-    // autorelease pool to simulate what happens on older versions.
-    [[[panel delegate] retain] autorelease];
-
-    // This will cause the `SelectFileDialogImpl` destructor to be called, and
-    // it will tear down the `SelectFileDialogBridge` via a Mojo IPC.
-    ResetDialog();
-
-    // The `SelectFileDialogBridge` destructor invokes `[panel cancel]`. That
-    // should close the panel, and run the completion handler.
-    EXPECT_EQ(nil, [panel delegate]);
-    EXPECT_FALSE([panel isVisible]);
-  }
-  EXPECT_EQ(nil, [panel delegate]);
-}
-
 }  // namespace ui::test
diff --git a/ui/views/controls/animated_image_view.cc b/ui/views/controls/animated_image_view.cc
index 53805316..fcdd618 100644
--- a/ui/views/controls/animated_image_view.cc
+++ b/ui/views/controls/animated_image_view.cc
@@ -64,9 +64,13 @@
   set_check_active_duration(playback_config->style !=
                             lottie::Animation::Style::kLoop);
 
-  SetCompositorFromWidget();
-
-  animated_image_->Start(std::move(playback_config));
+  if (GetWidget()) {
+    DoPlay(std::move(*playback_config));
+  } else {
+    // Playback will start in `AddedToWidget`.
+    playback_config_ = std::make_unique<lottie::Animation::PlaybackConfig>(
+        std::move(*playback_config));
+  }
 }
 
 void AnimatedImageView::Stop() {
@@ -118,6 +122,13 @@
   }
 }
 
+void AnimatedImageView::AddedToWidget() {
+  if (state_ == State::kPlaying && playback_config_) {
+    DoPlay(std::move(*playback_config_));
+    playback_config_.reset();
+  }
+}
+
 void AnimatedImageView::RemovedFromWidget() {
   if (compositor_) {
     Stop();
@@ -139,6 +150,12 @@
   }
 }
 
+void AnimatedImageView::DoPlay(
+    lottie::Animation::PlaybackConfig playback_config) {
+  SetCompositorFromWidget();
+  animated_image_->Start(std::move(playback_config));
+}
+
 void AnimatedImageView::SetCompositorFromWidget() {
   DCHECK(!compositor_);
   auto* widget = GetWidget();
diff --git a/ui/views/controls/animated_image_view.h b/ui/views/controls/animated_image_view.h
index 4112458..6f54521 100644
--- a/ui/views/controls/animated_image_view.h
+++ b/ui/views/controls/animated_image_view.h
@@ -58,10 +58,8 @@
   // will result in stopping the current animation.
   void SetAnimatedImage(std::unique_ptr<lottie::Animation> animated_image);
 
-  // Plays the animation and must only be called when this view has
-  // access to a widget.
-  //
-  // If a null |playback_config| is provided, the default one is used.
+  // Plays the animation. If a null |playback_config| is provided, the default
+  // one is used.
   void Play(absl::optional<lottie::Animation::PlaybackConfig> playback_config =
                 absl::nullopt);
 
@@ -87,6 +85,7 @@
   // Overridden from View:
   void OnPaint(gfx::Canvas* canvas) override;
   void NativeViewHierarchyChanged() override;
+  void AddedToWidget() override;
   void RemovedFromWidget() override;
 
   // Overridden from ui::CompositorAnimationObserver:
@@ -96,12 +95,17 @@
   // Overridden from ImageViewBase:
   gfx::Size GetImageSize() const override;
 
+  void DoPlay(lottie::Animation::PlaybackConfig playback_config);
   void SetCompositorFromWidget();
   void ClearCurrentCompositor();
 
   // The current state of the animation.
   State state_ = State::kStopped;
 
+  // playback_config_ stores the config while the object is waiting to be added
+  // to a widget.
+  std::unique_ptr<lottie::Animation::PlaybackConfig> playback_config_;
+
   // The compositor associated with the widget of this view.
   raw_ptr<ui::Compositor> compositor_ = nullptr;
 
diff --git a/ui/views/controls/animated_image_view_unittest.cc b/ui/views/controls/animated_image_view_unittest.cc
index a87a2243..9f9c827 100644
--- a/ui/views/controls/animated_image_view_unittest.cc
+++ b/ui/views/controls/animated_image_view_unittest.cc
@@ -142,5 +142,17 @@
   EXPECT_THAT(translate_op->dy, FloatEq(kExpectedDefaultOrigin - 5));
 }
 
+TEST_F(AnimatedImageViewTest, PlayBeforeWidget) {
+  auto animated_view = std::make_unique<AnimatedImageView>();
+  animated_view->SetAnimatedImage(CreateAnimationWithSize(gfx::Size(80, 80)));
+  // It should be valid to call `Play` before `animated_view` has been added to
+  // a widget.
+  animated_view->Play();
+
+  view_ = widget_.SetContentsView(std::move(animated_view));
+  view_->SetUseDefaultFillLayout(true);
+  widget_.Show();
+}
+
 }  // namespace
 }  // namespace views
diff --git a/ui/views/controls/editable_combobox/editable_combobox.h b/ui/views/controls/editable_combobox/editable_combobox.h
index 054ef76..b07e6c87 100644
--- a/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/ui/views/controls/editable_combobox/editable_combobox.h
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/functional/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "build/build_config.h"
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index 7ea833d9..e5daeb5 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -235,7 +235,7 @@
   cursor_view->GetViewAccessibility().OverrideIsIgnored(true);
   cursor_view_ = AddChildView(std::move(cursor_view));
   GetRenderText()->SetFontList(GetDefaultFontList());
-  UpdateBorder();
+  UpdateDefaultBorder();
   SetFocusBehavior(FocusBehavior::ALWAYS);
   views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
                                                 GetCornerRadius());
@@ -294,6 +294,8 @@
     SetColor(GetTextColor());
     UpdateBackgroundColor();
   }
+
+  UpdateDefaultBorder();
   OnPropertyChanged(&read_only_, kPropertyEffectsPaint);
 }
 
@@ -558,7 +560,7 @@
   if (invalid == invalid_)
     return;
   invalid_ = invalid;
-  UpdateBorder();
+  UpdateDefaultBorder();
   if (FocusRing::Get(this))
     FocusRing::Get(this)->SetInvalid(invalid);
   OnPropertyChanged(&invalid_, kPropertyEffectsNone);
@@ -574,7 +576,7 @@
 
 void Textfield::SetExtraInsets(const gfx::Insets& insets) {
   extra_insets_ = insets;
-  UpdateBorder();
+  UpdateDefaultBorder();
 }
 
 void Textfield::FitToLocalBounds() {
@@ -599,6 +601,13 @@
   UpdateAfterChange(TextChangeType::kNone, true);
 }
 
+bool Textfield::GetUseDefaultBorder() const {
+  return use_default_border_;
+}
+void Textfield::SetUseDefaultBorder(bool use_default_border) {
+  use_default_border_ = use_default_border;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Textfield, View overrides:
 
@@ -625,6 +634,7 @@
 void Textfield::SetBorder(std::unique_ptr<Border> b) {
   FocusRing::Remove(this);
   View::SetBorder(std::move(b));
+  use_default_border_ = false;
 }
 
 ui::Cursor Textfield::GetCursor(const ui::MouseEvent& event) {
@@ -2507,7 +2517,12 @@
   OnPropertyChanged(&model_ + kTextfieldBackgroundColor, kPropertyEffectsPaint);
 }
 
-void Textfield::UpdateBorder() {
+void Textfield::UpdateDefaultBorder() {
+  // Only update the border if SetBorder() has not been called. This is to avoid
+  // overriding any custom borders.
+  if (!use_default_border_) {
+    return;
+  }
   auto border = std::make_unique<views::FocusableBorder>();
   const LayoutProvider* provider = LayoutProvider::Get();
   border->SetColorId(ui::kColorTextfieldOutline);
@@ -2830,6 +2845,7 @@
 void Textfield::OnEnabledChanged() {
   if (GetInputMethod())
     GetInputMethod()->OnTextInputTypeChanged(this);
+  UpdateDefaultBorder();
 }
 
 void Textfield::DropDraggedText(
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
index 6bae17b..55eb4f9b 100644
--- a/ui/views/controls/textfield/textfield.h
+++ b/ui/views/controls/textfield/textfield.h
@@ -316,6 +316,10 @@
   // updating the cursor position and visibility.
   void FitToLocalBounds();
 
+  // Getter/Setter methods for |use_default_border_|.
+  bool GetUseDefaultBorder() const;
+  void SetUseDefaultBorder(bool use_default_border);
+
   // View overrides:
   int GetBaseline() const override;
   gfx::Size CalculatePreferredSize() const override;
@@ -561,8 +565,10 @@
   // Updates the painted background color.
   void UpdateBackgroundColor();
 
-  // Updates the border per the state of |invalid_|.
-  void UpdateBorder();
+  // Updates the border per the state of the textfield (i.e. Normal, Invalid,
+  // Readonly, Disabled). This will not do anything if a custom border has been
+  // set by SetBorder().
+  void UpdateDefaultBorder();
 
   // Updates the selection text color.
   void UpdateSelectionTextColor();
@@ -826,6 +832,10 @@
   // directionality.
   bool force_text_directionality_ = false;
 
+  // Helper flag that tracks whether SetBorder was called with a custom
+  // border.
+  bool use_default_border_ = true;
+
   // Holds the subscription object for the enabled changed callback.
   base::CallbackListSubscription enabled_changed_subscription_ =
       AddEnabledChangedCallback(
@@ -856,6 +866,7 @@
 VIEW_BUILDER_PROPERTY(SkColor, TextColor)
 VIEW_BUILDER_PROPERTY(int, TextInputFlags)
 VIEW_BUILDER_PROPERTY(ui::TextInputType, TextInputType)
+VIEW_BUILDER_PROPERTY(bool, UseDefaultBorder)
 END_VIEW_BUILDER
 
 }  // namespace views
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 930d346..fea9b10 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -1127,8 +1127,7 @@
 }
 
 #if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
-// TODO(crbug.com/1438286): Re-enable this test
-TEST_F(WidgetTestInteractive, DISABLED_InactiveWidgetDoesNotGrabActivation) {
+TEST_F(WidgetTestInteractive, InactiveWidgetDoesNotGrabActivation) {
   UniqueWidgetPtr widget = base::WrapUnique(CreateTopLevelPlatformWidget());
   ShowSync(widget.get());
   EXPECT_EQ(GetWidgetShowState(widget.get()), ui::SHOW_STATE_NORMAL);
diff --git a/weblayer/browser/password_manager_driver_factory.cc b/weblayer/browser/password_manager_driver_factory.cc
index 3b0ee1a..d37e12d 100644
--- a/weblayer/browser/password_manager_driver_factory.cc
+++ b/weblayer/browser/password_manager_driver_factory.cc
@@ -75,7 +75,7 @@
                                const gfx::RectF& bounds) override {}
 
 #if BUILDFLAG(IS_ANDROID)
-  void ShowTouchToFill(
+  void ShowKeyboardReplacingSurface(
       autofill::mojom::SubmissionReadinessState submission_readiness) override {
   }
 #endif
diff --git a/weblayer/browser/webapps/webapk_install_scheduler.cc b/weblayer/browser/webapps/webapk_install_scheduler.cc
index 038d242..e97003b 100644
--- a/weblayer/browser/webapps/webapk_install_scheduler.cc
+++ b/weblayer/browser/webapps/webapk_install_scheduler.cc
@@ -27,11 +27,9 @@
 WebApkInstallScheduler::WebApkInstallScheduler(
     const webapps::ShortcutInfo& shortcut_info,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     WebApkInstallFinishedCallback callback)
     : webapps_client_callback_(std::move(callback)),
-      primary_icon_(primary_icon),
-      is_primary_icon_maskable_(is_primary_icon_maskable) {
+      primary_icon_(primary_icon) {
   shortcut_info_ = std::make_unique<webapps::ShortcutInfo>(shortcut_info);
 }
 
@@ -42,14 +40,12 @@
     content::WebContents* web_contents,
     const webapps::ShortcutInfo& shortcut_info,
     const SkBitmap& primary_icon,
-    bool is_primary_icon_maskable,
     WebApkInstallFinishedCallback callback) {
   // Self owned WebApkInstallScheduler that destroys itself as soon as its
   // OnResult function is called when the scheduled installation failed or
   // finished.
-  WebApkInstallScheduler* scheduler =
-      new WebApkInstallScheduler(shortcut_info, primary_icon,
-                                 is_primary_icon_maskable, std::move(callback));
+  WebApkInstallScheduler* scheduler = new WebApkInstallScheduler(
+      shortcut_info, primary_icon, std::move(callback));
   scheduler->FetchMurmur2Hashes(web_contents);
 }
 
@@ -91,7 +87,7 @@
 
   webapps::BuildProto(
       *shortcut_info_.get(), shortcut_info_->manifest_id,
-      std::string() /* primary_icon_data */, is_primary_icon_maskable_,
+      std::string() /* primary_icon_data */,
       std::string() /* splash_icon_data */, "" /* package_name */,
       "" /* version */, std::move(*hashes), false /* is_manifest_stale */,
       false /* is_app_identity_update_supported */,
@@ -102,7 +98,8 @@
 void WebApkInstallScheduler::ScheduleWithChrome(
     std::unique_ptr<std::string> serialized_proto) {
   WebApkInstallSchedulerBridge::ScheduleWebApkInstallWithChrome(
-      std::move(serialized_proto), primary_icon_, is_primary_icon_maskable_,
+      std::move(serialized_proto), primary_icon_,
+      shortcut_info_->is_primary_icon_maskable,
       base::BindOnce(&WebApkInstallScheduler::OnResult,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/weblayer/browser/webapps/webapk_install_scheduler.h b/weblayer/browser/webapps/webapk_install_scheduler.h
index 54159007..e6ad388 100644
--- a/weblayer/browser/webapps/webapk_install_scheduler.h
+++ b/weblayer/browser/webapps/webapk_install_scheduler.h
@@ -47,7 +47,6 @@
       content::WebContents* web_contents,
       const webapps::ShortcutInfo& shortcut_info,
       const SkBitmap& primary_icon,
-      bool is_primary_icon_maskable,
       WebApkInstallFinishedCallback callback);
 
   void FetchProtoAndScheduleInstallForTesting(
@@ -58,7 +57,6 @@
  private:
   WebApkInstallScheduler(const webapps::ShortcutInfo& shortcut_info,
                          const SkBitmap& primary_icon,
-                         bool is_primary_icon_maskable,
                          WebApkInstallFinishedCallback callback);
 
   friend class TestWebApkInstallScheduler;
@@ -77,7 +75,6 @@
   WebApkInstallFinishedCallback webapps_client_callback_;
   std::unique_ptr<webapps::ShortcutInfo> shortcut_info_;
   const SkBitmap primary_icon_;
-  bool is_primary_icon_maskable_;
 
   // Used to get |weak_ptr_|.
   base::WeakPtrFactory<WebApkInstallScheduler> weak_ptr_factory_{this};
diff --git a/weblayer/browser/webapps/webapk_install_scheduler_browsertest.cc b/weblayer/browser/webapps/webapk_install_scheduler_browsertest.cc
index c0d2b9f..9970784 100644
--- a/weblayer/browser/webapps/webapk_install_scheduler_browsertest.cc
+++ b/weblayer/browser/webapps/webapk_install_scheduler_browsertest.cc
@@ -45,11 +45,9 @@
  public:
   TestWebApkInstallScheduler(const webapps::ShortcutInfo& shortcut_info,
                              const SkBitmap& primary_icon,
-                             bool is_primary_icon_maskable,
                              WebApkInstallFinishedCallback callback)
       : WebApkInstallScheduler(shortcut_info,
                                primary_icon,
-                               is_primary_icon_maskable,
                                std::move(callback)) {}
 
   TestWebApkInstallScheduler(const TestWebApkInstallScheduler&) = delete;
@@ -159,7 +157,7 @@
       webapps::ShortcutInfo info) {
     std::unique_ptr<TestWebApkInstallScheduler> scheduler_bridge(
         new TestWebApkInstallScheduler(
-            info, SkBitmap(), false,
+            info, SkBitmap(),
             base::BindOnce(&WebApkInstallSchedulerTest::OnInstallFinished,
                            base::Unretained(this))));
     return scheduler_bridge;
diff --git a/weblayer/browser/webapps/weblayer_webapps_client.cc b/weblayer/browser/webapps/weblayer_webapps_client.cc
index ed9c9c2..f3a5d34 100644
--- a/weblayer/browser/webapps/weblayer_webapps_client.cc
+++ b/weblayer/browser/webapps/weblayer_webapps_client.cc
@@ -100,7 +100,6 @@
   current_install_ids_.insert(params.shortcut_info->manifest_id);
   WebApkInstallScheduler::FetchProtoAndScheduleInstall(
       web_contents, *(params.shortcut_info), params.primary_icon,
-      params.has_maskable_primary_icon,
       base::BindOnce(&WebLayerWebappsClient::OnInstallFinished,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -112,7 +111,7 @@
 
   webapps::addShortcutToHomescreen(
       base::Uuid::GenerateRandomV4().AsLowercaseString(), info.url,
-      info.user_title, params.primary_icon, params.has_maskable_primary_icon);
+      info.user_title, params.primary_icon, info.is_primary_icon_maskable);
 }
 
 void WebLayerWebappsClient::OnInstallFinished(GURL manifest_id) {